//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/coregui/Views/ImportDataWidgets/RealDataTreeModel.cpp
//! @brief     Implements class RealDataTreeModel
//!
//! @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/coregui/Views/ImportDataWidgets/RealDataTreeModel.h"
#include "GUI/coregui/Models/RealDataItem.h"
#include "GUI/coregui/Models/RealDataModel.h"
#include <QApplication>
#include <QtCore>
#include <QtGui>

void RealDataTreeModel::setRealDataModel(RealDataModel* model)
{
    m_model = model;
    refreshAfterModelChange();

    // In the following connections: "queued" is important, because while
    // the notification is sent, a new item is still not completely constructed
    connect(m_model, &RealDataModel::realDataAddedOrRemoved, this,
            &RealDataTreeModel::refreshAfterModelChange,
            Qt::ConnectionType(Qt::QueuedConnection | Qt::UniqueConnection));

    connect(m_model, &RealDataModel::modelAboutToBeReset, this, &RealDataTreeModel::clear,
            Qt::UniqueConnection);
}

void RealDataTreeModel::refreshAfterModelChange()
{
    if (m_items1D == m_model->realDataItems1D() && m_items2D == m_model->realDataItems2D())
        return;

    beginResetModel();
    m_items1D = m_model->realDataItems1D();
    m_items2D = m_model->realDataItems2D();
    endResetModel();
    updateSubsriptions();
}

void RealDataTreeModel::clear()
{
    beginResetModel();
    m_items1D.clear();
    m_items2D.clear();
    endResetModel();
    updateSubsriptions();
}

void RealDataTreeModel::removeItem(RealDataItem* item)
{
    QModelIndex index = indexForItem(item);
    if (!index.isValid())
        return;

    if (item->dataLoader())
        item->dataLoader()->disconnect(this);

    if (item->isSpecularData()) {
        m_intermediate1DHeadline = m_items1D.size() == 1; // we need intermediate headline
                                                          // if last child gets deleted
        const int rowOfItem = m_items1D.indexOf(item);
        beginRemoveRows(create1DHeadlineIndex(), rowOfItem, rowOfItem);
        m_items1D.removeAll(item);
        m_model->removeItem(item);
        endRemoveRows();

        if (m_items1D.isEmpty()) {
            const int rowOfHeadline = 0;
            beginRemoveRows(QModelIndex(), rowOfHeadline, rowOfHeadline);
            m_intermediate1DHeadline = false;
            endRemoveRows();
        }
    } else {
        m_intermediate2DHeadline = m_items2D.size() == 1; // we need intermediate headline
                                                          // if last child gets deleted
        const int rowOfItem = m_items2D.indexOf(item);
        beginRemoveRows(create2DHeadlineIndex(), rowOfItem, rowOfItem);
        m_items2D.removeAll(item);
        m_model->removeItem(item);
        endRemoveRows();

        if (m_items2D.isEmpty()) {
            const int rowOfHeadline = m_items1D.isEmpty() ? 0 : 1;
            beginRemoveRows(QModelIndex(), rowOfHeadline, rowOfHeadline);
            m_intermediate2DHeadline = false;
            endRemoveRows();
        }
    }

    m_intermediate1DHeadline = false;
    m_intermediate2DHeadline = false;
}

RealDataItem* RealDataTreeModel::insertSpecularDataItem()
{
    auto newItem = m_model->insertSpecularDataItem();
    const int rowOfItem = m_model->realDataItems1D().indexOf(newItem);
    if (m_items1D.isEmpty()) {
        const int rowOfHeadline = 0;
        beginInsertRows(QModelIndex(), rowOfHeadline, rowOfHeadline);
        m_intermediate1DHeadline = true;
        endInsertRows();
    }

    beginInsertRows(create1DHeadlineIndex(), rowOfItem, rowOfItem);
    m_items1D = m_model->realDataItems1D();
    endInsertRows();
    updateSubsriptions();
    m_intermediate1DHeadline = false; // reset intermediate state
    return newItem;
}

RealDataItem* RealDataTreeModel::insertIntensityDataItem()
{
    auto newItem = m_model->insertIntensityDataItem();
    const int rowOfItem = m_model->realDataItems2D().indexOf(newItem);
    if (m_items2D.isEmpty()) {
        const int rowOfHeadline = m_items1D.isEmpty() ? 0 : 1;
        beginInsertRows(QModelIndex(), rowOfHeadline, rowOfHeadline);
        m_intermediate2DHeadline = true;
        endInsertRows();
    }

    beginInsertRows(create2DHeadlineIndex(), rowOfItem, rowOfItem);
    m_items2D = m_model->realDataItems2D();
    endInsertRows();
    updateSubsriptions();
    m_intermediate2DHeadline = false; // reset intermediate state
    return newItem;
}

RealDataItem* RealDataTreeModel::topMostItem() const
{
    if (!m_items1D.isEmpty())
        return m_items1D.first();
    if (!m_items2D.isEmpty())
        return m_items2D.first();
    return nullptr;
}

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

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

    const bool parentIs1DHeadline = (parent.row() == 0) && !m_items1D.isEmpty();
    if (parentIs1DHeadline)
        return createIndex(row, column, m_items1D[row]);

    return createIndex(row, column, m_items2D[row]);
}

QModelIndex RealDataTreeModel::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 create1DHeadlineIndex();

    return create2DHeadlineIndex();
}

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

int RealDataTreeModel::rowCount(const QModelIndex& parent) const
{
    const bool has1D = !m_items1D.isEmpty();
    const bool has2D = !m_items2D.isEmpty();

    if (!parent.isValid()) {
        int headLines = 0;
        if (has1D || m_intermediate1DHeadline)
            headLines++;
        if (has2D || m_intermediate2DHeadline)
            headLines++;

        return headLines;
    }

    if (parent.internalPointer() != nullptr)
        return 0; // items do not have children

    // parent is a headline
    if (parent.row() == 0 && has1D)
        return m_items1D.size();

    return m_items2D.size();
}

QVariant RealDataTreeModel::data(const QModelIndex& index, int role) const
{
    const bool has1D = !m_items1D.isEmpty();

    if (isHeadline(index)) {
        const QString title = (index.row() == 0 && has1D) ? "1D Data" : "2D 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 QBrush(QColor(250, 250, 250));

        case Qt::ForegroundRole:
            return QBrush(Qt::black);

        default:
            return QVariant();
        }
    }

    const auto item = itemForIndex(index);

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

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

    if (role == Qt::DisplayRole) {
        const auto numErrors =
            (item->dataLoader() != nullptr) ? item->dataLoader()->numErrors() : 0;

        if (numErrors == 0)
            return item->name();
        else if (numErrors == 1)
            return item->name() + " (1 parser warning)";
        else
            return item->name() + QString(" (%1 parser warnings)").arg(numErrors);
    }

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

    return m_model->data(m_model->indexOfItem(item), role);
}

Qt::ItemFlags RealDataTreeModel::flags(const QModelIndex& index) const
{
    if (isHeadline(index))
        return Qt::NoItemFlags;

    auto f = m_model->flags(m_model->indexOfItem(itemForIndex(index)));
    if (index.column() == 0) // col 0 contains name of the data entry
        f |= Qt::ItemIsEditable;

    return f;
}

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

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

    return false;
}

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

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

QModelIndex RealDataTreeModel::indexForItem(RealDataItem* item) const
{
    if (item == nullptr)
        return QModelIndex();

    if (item->isSpecularData()) {
        if (auto index = m_items1D.indexOf(item); index >= 0)
            return createIndex(index, 0, item);

        return QModelIndex();
    }

    if (auto index = m_items2D.indexOf(item); index >= 0)
        return createIndex(index, 0, item);

    return QModelIndex();
}

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

    return index.internalPointer() == nullptr;
}

QModelIndex RealDataTreeModel::create1DHeadlineIndex() const
{
    return createIndex(0, 0, nullptr);
}

QModelIndex RealDataTreeModel::create2DHeadlineIndex() const
{
    const bool has1D = !m_items1D.isEmpty();
    return createIndex(has1D ? 1 : 0, 0, nullptr);
}

void RealDataTreeModel::updateSubsriptions()
{
    for (auto item : m_items1D)
        connect(item, &RealDataItem::importContentsProcessed, this,
                std::bind(&RealDataTreeModel::onContentsProcessed, this, item),
                Qt::UniqueConnection);

    for (auto item : m_items2D)
        connect(item, &RealDataItem::importContentsProcessed, this,
                std::bind(&RealDataTreeModel::onContentsProcessed, this, item),
                Qt::UniqueConnection);
}

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