//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Instrument/InstrumentsTreeModel.cpp
//! @brief     Implements class InstrumentsTreeModel
//!
//! @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/View/Instrument/InstrumentsTreeModel.h"
#include "GUI/Application/ApplicationSettings.h"
#include "GUI/Model/Device/InstrumentItems.h"
#include "GUI/Model/Device/InstrumentModel.h"
#include <QApplication>
#include <QtGui>

InstrumentsTreeModel::InstrumentsTreeModel(QObject* parent, InstrumentModel* model)
    : QAbstractItemModel(parent)
    , m_model(model)
    , m_visibleTypes(All)
    , m_namesAreEditable(false)
    , m_enableEmptyHeadlines(true)
{
}

void InstrumentsTreeModel::enableEmptyHeadlines(bool b)
{
    if (b != m_enableEmptyHeadlines) {
        beginResetModel();
        m_enableEmptyHeadlines = b;
        endResetModel();
    }
}

void InstrumentsTreeModel::setTypeEnabled(InstrumentType type, bool b)
{
    if (m_visibleTypes.testFlag(type) != b) {
        beginResetModel();
        m_visibleTypes.setFlag(type, b);
        endResetModel();
    }
}

void InstrumentsTreeModel::clear()
{
    beginResetModel();
    endResetModel();
}

QList<InstrumentsTreeModel::InstrumentType> InstrumentsTreeModel::visibleTypes() const
{
    QList<InstrumentsTreeModel::InstrumentType> result;

    const auto forType = [&](InstrumentType type) {
        if (m_visibleTypes.testFlag(type)
            && (m_enableEmptyHeadlines || !instrumentItemsOfType(type).isEmpty()))
            result << type;
    };

    forType(Gisas);
    forType(Offspec);
    forType(Specular);
    forType(Depthprobe);

    return result;
}

QVector<InstrumentItem*> InstrumentsTreeModel::instrumentItemsOfType(InstrumentType type) const
{
    switch (type) {
    case Gisas:
        return m_model->instrumentItems([](const InstrumentItem* p) {
            return dynamic_cast<const GISASInstrumentItem*>(p) != nullptr;
        });
    case Offspec:
        return m_model->instrumentItems([](const InstrumentItem* p) {
            return dynamic_cast<const OffspecInstrumentItem*>(p) != nullptr;
        });
    case Specular:
        return m_model->instrumentItems([](const InstrumentItem* p) {
            return dynamic_cast<const SpecularInstrumentItem*>(p) != nullptr;
        });
    case Depthprobe:
        return m_model->instrumentItems([](const InstrumentItem* p) {
            return dynamic_cast<const DepthprobeInstrumentItem*>(p) != nullptr;
        });
    default:
        return {};
    }
}

InstrumentItem* InstrumentsTreeModel::topMostItem() const
{
    for (auto t : visibleTypes())
        if (auto instr = instrumentItemsOfType(t); !instr.isEmpty())
            return instr.first();

    return nullptr;
}

QModelIndex InstrumentsTreeModel::indexOfHeadline(InstrumentType type) const
{
    int row = 0;
    for (auto t : visibleTypes()) {
        if (t == type)
            return createIndex(row, 0, nullptr);
        row++;
    }

    return QModelIndex();
}

QModelIndex InstrumentsTreeModel::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 type : visibleTypes())
        if (parent == indexOfHeadline(type))
            return createIndex(row, column, instrumentItemsOfType(type)[row]);

    return QModelIndex();
}

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

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

    auto* item = itemForIndex(index);
    for (auto type : visibleTypes())
        if (instrumentItemsOfType(type).contains(item))
            return indexOfHeadline(type);

    return QModelIndex();
}

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

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

    // parent is a headline
    for (auto type : visibleTypes())
        if (parent == indexOfHeadline(type))
            return instrumentItemsOfType(type).size();

    return 0;
}

QVariant InstrumentsTreeModel::data(const QModelIndex& index, int role) const
{
    if (isHeadline(index)) {
        QString title;
        if (index == indexOfHeadline(Gisas))
            title = "GISAS";
        else if (index == indexOfHeadline(Offspec))
            title = "Offspec";
        else if (index == indexOfHeadline(Specular))
            title = "Specular";
        else if (index == indexOfHeadline(Depthprobe))
            title = "Depthprobe";

        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 item->description();

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

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

    if (role == Qt::TextAlignmentRole)
        return QVariant(Qt::AlignLeft | Qt::AlignTop);

    if (role == Qt::DecorationRole)
        switch (instrumentType(item)) {
        case Gisas:
            return QIcon(":/images/gisas_instrument.svg");
        case Offspec:
            return QIcon(":/images/offspec_instrument.svg");
        case Specular:
            return QIcon(":/images/specular_instrument.svg");
        case Depthprobe:
            return QIcon(":/images/depth_instrument.svg");
        default:
            break;
        }

    return QVariant();
}

Qt::ItemFlags InstrumentsTreeModel::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 && m_namesAreEditable) // col 0 contains name of the data entry
        f |= Qt::ItemIsEditable;

    return f;
}

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

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

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

    return false;
}

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

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

QModelIndex InstrumentsTreeModel::indexForItem(InstrumentItem* item) const
{
    if (item == nullptr)
        return QModelIndex();

    for (auto type : visibleTypes())
        if (auto row = instrumentItemsOfType(type).indexOf(item); row >= 0)
            return createIndex(row, 0, item);

    return QModelIndex();
}

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

    beginRemoveRows(index.parent(), index.row(), index.row());
    m_model->removeInstrument(item);
    endRemoveRows();
}

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

    return index.internalPointer() == nullptr;
}

InstrumentsTreeModel::InstrumentType InstrumentsTreeModel::instrumentType(InstrumentItem* item)
{
    if (item->is<GISASInstrumentItem>())
        return Gisas;

    if (item->is<OffspecInstrumentItem>())
        return Offspec;

    if (item->is<SpecularInstrumentItem>())
        return Specular;

    if (item->is<DepthprobeInstrumentItem>())
        return Depthprobe;

    ASSERT(false);
    return None;
}
