//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/Model/Model/RealTreeModel.cpp
//! @brief     Implements class RealTreeModel
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2021
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/Model/Model/RealTreeModel.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/Device/RealItem.h"
#include "GUI/Model/Model/RealModel.h"
#include <QApplication>
#include <QtGui>

RealTreeModel::RealTreeModel(QObject* parent, RealModel* model)
    : QAbstractItemModel(parent)
    , m_model(model)
    , m_visibleRanks({1, 2})
{
    for (auto rank : m_visibleRanks)
        m_items[rank - 1] = model->realItems(rank);

    updateSubscriptions();
}

void RealTreeModel::setVisibleRanks(QSet<int> visibleRanks)
{
    if (m_visibleRanks == visibleRanks)
        return;

    m_visibleRanks = visibleRanks;

    beginResetModel();
    for (auto rank : m_visibleRanks)
        m_items[rank - 1] = m_model->realItems(rank);
    endResetModel();

    updateSubscriptions();
}

void RealTreeModel::refreshAfterModelChange()
{
    for (auto rank : m_visibleRanks) {
        if (!m_items[rank - 1].isEmpty()) {
            beginRemoveRows(indexOfHeadline(rank), 0, m_items[rank - 1].size() - 1);
            m_items[rank - 1] = m_model->realItems(rank);
            endRemoveRows();
        }
    }

    updateSubscriptions();
}

void RealTreeModel::removeDataItem(RealItem* item)
{
    QModelIndex index = indexForItem(item);
    if (!index.isValid())
        return;

    const int rank = item->isSpecularData() ? 1 : 2;
    if (m_visibleRanks.contains(rank)) {
        const int rowOfItem = m_items[rank - 1].indexOf(item);
        beginRemoveRows(indexOfHeadline(rank), rowOfItem, rowOfItem);
        m_items[rank - 1].removeAll(item);
        m_model->removeRealItem(item);
        endRemoveRows();
    }
}

RealItem* RealTreeModel::injectDataItem(int rank)
{
    auto* newItem = m_model->insertDataItem(rank);
    if (!m_visibleRanks.contains(rank))
        return newItem;

    const int rowOfItem = m_model->realItems(rank).indexOf(newItem);
    beginInsertRows(indexOfHeadline(rank), rowOfItem, rowOfItem);
    m_items[rank - 1] = m_model->realItems(rank);
    endInsertRows();
    updateSubscriptions();
    return newItem;
}

RealItem* RealTreeModel::topMostItem() const
{
    if (!m_items[0].isEmpty() && m_visibleRanks.contains(1))
        return m_items[0].first();
    if (!m_items[1].isEmpty() && m_visibleRanks.contains(2))
        return m_items[1].first();
    return nullptr;
}

QModelIndex RealTreeModel::indexOfHeadline(int rank) const
{
    if (!m_visibleRanks.contains(rank))
        return QModelIndex();

    if (rank == 1)
        return createIndex(0, 0, nullptr);

    if (rank == 2) {
        const bool has1D = m_visibleRanks.contains(1);
        return createIndex(has1D ? 1 : 0, 0, nullptr);
    }

    return QModelIndex();
}

QModelIndex RealTreeModel::index(int row, int column, const QModelIndex& parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();

    if (!parent.isValid())
        return createIndex(row, column, nullptr);

    for (auto rank : m_visibleRanks)
        if (parent == indexOfHeadline(rank))
            return createIndex(row, column, m_items[rank - 1][row]);

    return QModelIndex();
}

QModelIndex RealTreeModel::parent(const QModelIndex& index) const
{
    if (!index.isValid())
        return QModelIndex();

    if (index.internalPointer() == nullptr) // index is headline => no parent
        return QModelIndex();

    if (itemForIndex(index)->isSpecularData())
        return indexOfHeadline(1);

    return indexOfHeadline(2);
}

int RealTreeModel::columnCount(const QModelIndex& /*parent*/) const
{
    return 1;
}

int RealTreeModel::rowCount(const QModelIndex& parent) const
{
    if (!parent.isValid())
        return m_visibleRanks.size();

    // parent is a headline
    for (int rank : m_visibleRanks)
        if (parent == indexOfHeadline(rank))
            return m_items[rank - 1].size();

    return 0;
}

QVariant RealTreeModel::data(const QModelIndex& index, int role) const
{
    if (isHeadline(index)) {
        QString title = (index == indexOfHeadline(1)) ? "1D Data" : "2D Data";
        if (m_visibleRanks.size() < 2)
            title = "Data";

        switch (role) {
        case Qt::DisplayRole:
            return title;

        case Qt::FontRole: {
            QFont f(QApplication::font());
            f.setPointSize(f.pointSize() * 1.5);
            f.setBold(true);
            return f;
        }

        case Qt::SizeHintRole: {
            QFont f(QApplication::font());
            f.setPointSize(f.pointSize() * 1.5);
            f.setBold(true);
            QSize s = QFontMetrics(f).boundingRect(title).size();
            return QSize(s.width() * 2, s.height() * 2);
        }

        case Qt::TextAlignmentRole:
            return QVariant(Qt::AlignLeft | Qt::AlignVCenter);

        case Qt::BackgroundRole:
            return appSettings->styleSheetPalette().color(QPalette::Base);

        case Qt::ForegroundRole:
            return appSettings->styleSheetPalette().color(QPalette::Text);

        default:
            return QVariant();
        }
    }

    auto* const item = itemForIndex(index);

    if (role == Qt::ToolTipRole)
        return QString();

    if (role == Qt::DecorationRole) {
        if (item->isSpecularData())
            return QIcon(":/images/1D_OK.png");
        return QIcon(":/images/2D_OK.png");
    }

    if (role == Qt::DisplayRole)
        return item->realItemName();

    if (role == Qt::EditRole)
        return item->realItemName();

    return QVariant();
}

Qt::ItemFlags RealTreeModel::flags(const QModelIndex& index) const
{
    if (isHeadline(index) || !index.isValid())
        return Qt::NoItemFlags;

    auto f = QAbstractItemModel::flags(index);
    f |= Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;

    if (index.column() == 0) // col 0 contains name of the data entry
        f |= Qt::ItemIsEditable;

    return f;
}

bool RealTreeModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!index.isValid())
        return false;

    if (role == Qt::EditRole && index.column() == 0) {
        itemForIndex(index)->setRealItemName(value.toString());
        emit dataChanged(index, index);
        return true;
    }

    return false;
}

RealItem* RealTreeModel::itemForIndex(const QModelIndex& index) const
{
    if (!index.isValid())
        return nullptr;

    return reinterpret_cast<RealItem*>(index.internalPointer());
}

QModelIndex RealTreeModel::indexForItem(RealItem* item) const
{
    if (item == nullptr)
        return QModelIndex();

    const int rank = item->isSpecularData() ? 1 : 2;
    if (m_visibleRanks.contains(rank))
        if (auto index = m_items[rank - 1].indexOf(item); index >= 0)
            return createIndex(index, 0, item);

    return QModelIndex();
}

bool RealTreeModel::isHeadline(const QModelIndex& index) const
{
    if (!index.isValid())
        return false;

    return index.internalPointer() == nullptr;
}

void RealTreeModel::updateSubscriptions()
{
    for (auto* item : m_items[0])
        connect(item, &RealItem::importContentsProcessed, this,
                [this, item]() { onContentsProcessed(item); });

    for (auto* item : m_items[1])
        connect(item, &RealItem::importContentsProcessed, this,
                [this, item]() { onContentsProcessed(item); });
}

void RealTreeModel::onContentsProcessed(RealItem* item)
{
    QModelIndex index = indexForItem(item);
    emit dataChanged(index, index);
}
