//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/MaterialEditor/MaterialEditorModel.cpp
//! @brief     Implements class MaterialEditorModel
//!
//! @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/MaterialEditor/MaterialEditorModel.h"
#include "GUI/Model/Sample/MaterialItem.h"
#include "GUI/Model/Sample/MaterialModel.h"
#include <QApplication>
#include <QFontMetrics>
#include <QPixmap>

MaterialEditorModel::MaterialEditorModel(MaterialModel* p)
    : m_model(p)
{
}

int MaterialEditorModel::rowCount(const QModelIndex& /*parent = QModelIndex()*/) const
{
    return m_model->materialItems().size();
}

int MaterialEditorModel::columnCount(const QModelIndex& /*parent = QModelIndex()*/) const
{
    return NUM_COLUMNS;
}

QVariant MaterialEditorModel::headerData(int section, Qt::Orientation /*orientation*/,
                                         int role /* = Qt::DisplayRole */) const
{
    if (role != Qt::DisplayRole)
        return {};

    switch (section) {
    case NAME:
        return "Name";
    case TYPE:
        return "Type";
    case PARAMETERS:
        return "Material parameters";
    case MAGNETIZATION:
        return "Magnetization [A/m]";
    default:
        return {};
    }
}

QVariant MaterialEditorModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
    if (!index.isValid())
        return {};

    const MaterialItem* material = m_model->materialItems()[index.row()];

    if (role == Qt::DisplayRole) {
        switch (index.column()) {
        case NAME:
            return material->matItemName();

        case TYPE:
            return material->hasRefractiveIndex() ? "Refractive based" : "SLD based";

        case PARAMETERS:
            if (material->hasRefractiveIndex())
                return QString("delta: %1, beta: %2").arg(material->delta()).arg(material->beta());
            else
                return QString("re: %1, im: %2").arg(material->sldRe()).arg(material->sldIm());

        case MAGNETIZATION:
            return QString("%1/%2/%3")
                .arg(material->magnetization().r3().x())
                .arg(material->magnetization().r3().y())
                .arg(material->magnetization().r3().z());
        }
    } else if (role == Qt::DecorationRole) {
        switch (index.column()) {
        case NAME: {
            const int size = qApp->fontMetrics().height();
            QPixmap pixmap(size, size);
            pixmap.fill(materialItemFromIndex(index)->color());
            return pixmap;
        }
        }
    }

    return {};
}

void MaterialEditorModel::setMaterialItemName(const QModelIndex& index, const QString& name)
{
    materialItemFromIndex(index)->setMatItemName(name);
    emit dataChanged(index, index);
}

void MaterialEditorModel::setColor(const QModelIndex& index, const QColor& color)
{
    materialItemFromIndex(index)->setColor(color);
    emit dataChanged(index, index);
}

void MaterialEditorModel::setX(const QModelIndex& index, double value)
{
    auto* material = materialItemFromIndex(index);
    R3 m = material->magnetization();
    m.setX(value);
    material->setMagnetization(m);
    const auto magIndex = this->index(index.row(), MAGNETIZATION);
    emit dataChanged(magIndex, magIndex);
}

void MaterialEditorModel::setY(const QModelIndex& index, double value)
{
    auto* material = materialItemFromIndex(index);
    R3 m = material->magnetization();
    m.setY(value);
    material->setMagnetization(m);
    const auto magIndex = this->index(index.row(), MAGNETIZATION);
    emit dataChanged(magIndex, magIndex);
}

void MaterialEditorModel::setZ(const QModelIndex& index, double value)
{
    auto* material = materialItemFromIndex(index);
    R3 m = material->magnetization();
    m.setZ(value);
    material->setMagnetization(m);
    const auto magIndex = this->index(index.row(), MAGNETIZATION);
    emit dataChanged(magIndex, magIndex);
}

void MaterialEditorModel::setDelta(const QModelIndex& index, double value)
{
    auto* m = materialItemFromIndex(index);
    m->setRefractiveIndex(value, m->beta());
    const auto paramIndex = this->index(index.row(), PARAMETERS);
    emit dataChanged(paramIndex, paramIndex);
}

void MaterialEditorModel::setBeta(const QModelIndex& index, double value)
{
    auto* m = materialItemFromIndex(index);
    m->setRefractiveIndex(m->delta(), value);
    const auto paramIndex = this->index(index.row(), PARAMETERS);
    emit dataChanged(paramIndex, paramIndex);
}

void MaterialEditorModel::setRe(const QModelIndex& index, double value)
{
    auto* m = materialItemFromIndex(index);
    m->setScatteringLengthDensity(complex_t(value, m->sldIm()));
    auto paramIndex = this->index(index.row(), PARAMETERS);
    emit dataChanged(paramIndex, paramIndex);
}

void MaterialEditorModel::setIm(const QModelIndex& index, double value)
{
    auto* m = materialItemFromIndex(index);
    m->setScatteringLengthDensity(complex_t(m->sldRe(), value));
    auto paramIndex = this->index(index.row(), PARAMETERS);
    emit dataChanged(paramIndex, paramIndex);
}

MaterialItem* MaterialEditorModel::materialItemFromIndex(const QModelIndex& index) const
{
    return index.isValid() ? m_model->materialItems()[index.row()] : nullptr;
}

QModelIndex MaterialEditorModel::indexFromMaterial(const QString& identifier) const
{
    const auto materials = m_model->materialItems();
    for (int row = 0; row < materials.size(); row++)
        if (materials[row]->identifier() == identifier)
            return index(row, 0);
    return QModelIndex();
}

QModelIndex MaterialEditorModel::indexFromMaterial(const MaterialItem* m) const
{
    const auto materials = m_model->materialItems();
    for (int row = 0; row < materials.size(); row++)
        if (materials[row] == m)
            return index(row, 0);
    return QModelIndex();
}

QModelIndex MaterialEditorModel::first() const
{
    return index(0, 0);
}

MaterialItem* MaterialEditorModel::addRefractiveMaterialItem(const QString& name, double delta,
                                                             double beta)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    auto* m = m_model->addRefractiveMaterialItem(name, delta, beta);
    endInsertRows();
    return m;
}

MaterialItem* MaterialEditorModel::addSLDMaterialItem(const QString& name, double sld,
                                                      double abs_term)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    auto* m = m_model->addSLDMaterialItem(name, sld, abs_term);
    endInsertRows();
    return m;
}

MaterialItem* MaterialEditorModel::cloneMaterialItem(const QModelIndex& index)
{
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    auto* m = m_model->copyMaterialItem(*materialItemFromIndex(index));
    endInsertRows();
    return m;
}

void MaterialEditorModel::removeMaterial(const QModelIndex& index)
{
    beginRemoveRows(QModelIndex(), index.row(), index.row());
    m_model->removeMaterialItem(materialItemFromIndex(index));
    endRemoveRows();
}
