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

MaterialsQModel::MaterialsQModel(MaterialsSet& model)
    : m_model(model)
{
}

int MaterialsQModel::rowCount(const QModelIndex& /*parent = QModelIndex()*/) const
{
    return m_model.size();
}

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

QVariant MaterialsQModel::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 MaterialsQModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const
{
    if (!index.isValid())
        return {};

    const MaterialItem* material = m_model.at(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 MaterialsQModel::setMaterialItemName(const QModelIndex& index, const QString& name)
{
    materialItemFromIndex(index)->setMatItemName(name);
    emit dataChanged(index, index);
}

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

void MaterialsQModel::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 MaterialsQModel::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 MaterialsQModel::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 MaterialsQModel::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 MaterialsQModel::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 MaterialsQModel::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 MaterialsQModel::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* MaterialsQModel::materialItemFromIndex(const QModelIndex& index) const
{
    return index.isValid() ? m_model.at(index.row()) : nullptr;
}

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

QModelIndex MaterialsQModel::indexFromMaterial(const MaterialItem* m) const
{
    for (size_t row = 0; row < m_model.size(); row++)
        if (m_model[row] == m)
            return index(row, 0);
    return {};
}

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

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

MaterialItem* MaterialsQModel::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;
}

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