//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/SampleDesigner/LayerEditorUtil.cpp
//! @brief     Implements class LayerEditorUtil
//!
//! @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/SampleDesigner/LayerEditorUtil.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Sample/CompoundItem.h"
#include "GUI/Model/Sample/CoreAndShellItem.h"
#include "GUI/Model/Sample/FormFactorItems.h"
#include "GUI/Model/Sample/MesocrystalItem.h"
#include "GUI/Model/Sample/ParticleItem.h"
#include "GUI/Model/Sample/ProfileItems.h"
#include "GUI/Model/Sample/RoughnessItems.h"
#include "GUI/View/Numeric/DoubleLineEdit.h"
#include "GUI/View/Numeric/DoubleSpinBox.h"
#include "GUI/View/SampleDesigner/CompoundForm.h"
#include "GUI/View/SampleDesigner/CoreAndShellForm.h"
#include "GUI/View/SampleDesigner/FormLayouter.h"
#include "GUI/View/SampleDesigner/MesocrystalForm.h"
#include "GUI/View/SampleDesigner/ParticleForm.h"
#include <QLabel>
#include <QMenu>
#include <QPushButton>

using std::function;

namespace {

void updateLabelUnit(QLabel* label, const QString& unit)
{
    QString text = label->text();
    const bool hasColon = text.indexOf(":") > 0;
    text = text.left(text.indexOf("["));
    text = text.trimmed();
    if (text.endsWith(":"))
        text.chop(1);

    if (!unit.isEmpty())
        text += " [" + unit + "]";
    if (hasColon)
        text += ":";
    label->setText(text);
}

} // namespace

void LayerEditorUtil::updateLabelUnit(QLabel* label, DoubleSpinBox* editor)
{
    ::updateLabelUnit(label, editor->displayUnitAsString());
}

void LayerEditorUtil::updateLabelUnit(QLabel* label)
{
    if (auto* editor = dynamic_cast<DoubleSpinBox*>(label->buddy()))
        ::updateLabelUnit(label, editor->displayUnitAsString());
    else if (auto* editor = dynamic_cast<DoubleLineEdit*>(label->buddy()))
        ::updateLabelUnit(label, editor->displayUnitAsString());
}

void LayerEditorUtil::addMultiPropertyToGrid(QGridLayout* m_gridLayout, int firstCol,
                                             const DoubleProperties& valueProperties,
                                             SampleEditorController* ec, bool vertically,
                                             bool addSpacer)
{
    const auto setNewValue = [ec](double newValue, DoubleProperty& d) {
        ec->setDouble(newValue, d);
    };

    addMultiPropertyToGrid(m_gridLayout, firstCol, valueProperties, setNewValue, vertically,
                           addSpacer);
}

void LayerEditorUtil::addMultiPropertyToGrid(QGridLayout* m_gridLayout, int firstCol,
                                             const DoubleProperties& valueProperties,
                                             function<void(double, DoubleProperty&)> setNewValue,
                                             bool vertically, bool addSpacer)
{
    int col = firstCol;
    for (auto* d : valueProperties) {
        DoubleSpinBox* editor = new DoubleSpinBox(*d);
        QObject::connect(editor, &DoubleSpinBox::baseValueChanged,
                         [setNewValue, d](double newValue) { setNewValue(newValue, *d); });

        QString labeltext = d->label();
        if (!vertically && !labeltext.endsWith(":"))
            labeltext += ":";
        auto* label = new QLabel(labeltext, m_gridLayout->parentWidget());
        label->setBuddy(editor); // necessary for unit-updating
        LayerEditorUtil::updateLabelUnit(label, editor);

        if (vertically) {
            m_gridLayout->addWidget(label, 0, col);
            m_gridLayout->addWidget(editor, 1, col);
            col++;
        } else {
            m_gridLayout->addWidget(label, 1, col++);
            m_gridLayout->addWidget(editor, 1, col++);
        }
    }
    if (addSpacer)
        m_gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, col);
}

void LayerEditorUtil::addMultiPropertyToGrid(QGridLayout* m_gridLayout, int firstCol,
                                             const DoubleProperties& valueProperties,
                                             SampleEditorController* ec, bool addSpacer)
{
    addMultiPropertyToGrid(m_gridLayout, firstCol, valueProperties, ec, valueProperties.size() > 1,
                           addSpacer);
}

void LayerEditorUtil::addVectorToGrid(QGridLayout* m_gridLayout, int firstCol, VectorProperty& v,
                                      SampleEditorController* ec, bool vertically, bool addSpacer)
{
    addMultiPropertyToGrid(m_gridLayout, firstCol, {&v.x(), &v.y(), &v.z()}, ec, vertically,
                           addSpacer);
}


void LayerEditorUtil::addVectorToGrid(QGridLayout* m_gridLayout, int firstCol, VectorProperty& v,
                                      function<void(double, DoubleProperty&)> setNewValue,
                                      bool vertically, bool addSpacer)
{
    addMultiPropertyToGrid(m_gridLayout, firstCol, {&v.x(), &v.y(), &v.z()}, setNewValue,
                           vertically, addSpacer);
}

void LayerEditorUtil::addMagnetizationToGrid(
    QGridLayout* m_gridLayout, int firstCol, VectorProperty& v,
    std::function<void(double, DoubleProperty&)> setNewValue, bool vertically, bool addSpacer,
    bool with_z)
{
    if (with_z)
        addMultiPropertyToGrid(m_gridLayout, firstCol, {&v.x(), &v.y(), &v.z()}, setNewValue,
                               vertically, addSpacer);
    else
        addMultiPropertyToGrid(m_gridLayout, firstCol, {&v.x(), &v.y()}, setNewValue, vertically,
                               addSpacer);
}

QLabel* LayerEditorUtil::createBoldLabel(const QString& text)
{
    auto* l = new QLabel(text);
    QFont f = l->font();
    f.setBold(true);
    l->setFont(f);
    return l;
}

DoubleProperties LayerEditorUtil::doublePropertiesOfItem(RotationItem* item)
{
    if (!item)
        return {};
    return item->rotationProperties();
}

DoubleProperties LayerEditorUtil::doublePropertiesOfItem(Profile2DItem* item)
{
    if (!item)
        return {};
    return item->profileProperties();
}

DoubleProperties LayerEditorUtil::doublePropertiesOfItem(Profile1DItem* item)
{
    if (!item)
        return {};
    return item->profileProperties();
}

DoubleProperties LayerEditorUtil::doublePropertiesOfItem(FormFactorItem* item)
{
    if (!item)
        return {};
    return item->geometryProperties();
}

DoubleProperties LayerEditorUtil::doublePropertiesOfItem(RoughnessItem* item)
{
    if (!item)
        return {};
    return item->roughnessProperties();
}

QWidget* LayerEditorUtil::createWidgetForItemWithParticles(QWidget* parentWidget,
                                                           ItemWithParticles* itemWithParticles,
                                                           bool allowAbundance,
                                                           SampleEditorController* ec,
                                                           bool allowRemove /*= true*/)
{
    if (auto* composition = dynamic_cast<CompoundItem*>(itemWithParticles))
        return new CompoundForm(parentWidget, composition, ec, allowRemove);

    if (auto* coreShell = dynamic_cast<CoreAndShellItem*>(itemWithParticles))
        return new CoreAndShellForm(parentWidget, coreShell, ec, allowRemove);

    if (auto* meso = dynamic_cast<MesocrystalItem*>(itemWithParticles))
        return new MesocrystalForm(parentWidget, meso, ec, allowRemove);

    if (auto* particle = dynamic_cast<ParticleItem*>(itemWithParticles))
        return new ParticleForm(parentWidget, particle, allowAbundance, ec, allowRemove);

    ASSERT(false);
    return nullptr;
}

QPushButton* LayerEditorUtil::createAddParticleButton(
    QWidget* parentWidget, std::function<void(FormFactorItemCatalog::Type t)> slotAddFormFactor,
    std::function<void(ItemWithParticlesCatalog::Type t)> slotAddParticle)
{
    auto* btn = new QPushButton("Add particle", parentWidget);

    auto* menu = new QMenu(btn);
    QMenu* menuForEntries = menu;

    const auto group = [&](const QString& title) { menuForEntries = menu->addMenu(title); };

    group("Hard particles");
    for (const auto type : FormFactorItemCatalog::hardParticleTypes()) {
        const auto ui = FormFactorItemCatalog::uiInfo(type);
        QAction* a = menuForEntries->addAction(QIcon(ui.iconPath), ui.menuEntry);
        a->setToolTip(ui.description);
        QObject::connect(a, &QAction::triggered, [=]() { slotAddFormFactor(type); });
    }

    group("Ripples");
    for (const auto type : FormFactorItemCatalog::rippleTypes()) {
        const auto ui = FormFactorItemCatalog::uiInfo(type);
        QAction* a = menuForEntries->addAction(QIcon(ui.iconPath), ui.menuEntry);
        a->setToolTip(ui.description);
        QObject::connect(a, &QAction::triggered, [=]() { slotAddFormFactor(type); });
    }

    group("Particle assemblies");
    for (const auto type :
         {ItemWithParticlesCatalog::Type::Composition, ItemWithParticlesCatalog::Type::CoreShell,
          ItemWithParticlesCatalog::Type::Mesocrystal}) {
        const auto ui = ItemWithParticlesCatalog::uiInfo(type);
        QAction* a = menuForEntries->addAction(QIcon(ui.iconPath), ui.menuEntry);
        a->setToolTip(ui.description);
        QObject::connect(a, &QAction::triggered, [=]() { slotAddParticle(type); });
    }

    btn->setMenu(menu);

    return btn;
}

QList<QColor> LayerEditorUtil::predefinedLayerColors()
{
    static QList<QColor> colors = {QColor(230, 255, 213), QColor(194, 252, 240),
                                   QColor(239, 228, 176), QColor(200, 191, 231),
                                   QColor(253, 205, 193), QColor(224, 193, 253)};

    return colors;
}
