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

void GUI::Util::Layer::addMultiPropertyToGrid(QGridLayout* m_grid_layout, 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_grid_layout, firstCol, valueProperties, setNewValue, vertically,
                           addSpacer);
}

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

        QString labeltext = d->label();
        if (!vertically && !labeltext.endsWith(":"))
            labeltext += ":";

        auto* label = new QLabel(labeltext, m_grid_layout->parentWidget());
        label->setBuddy(editor); // necessary for unit-updating

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

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

QWidget* GUI::Util::Layer::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_NEVER;
    return nullptr;
}

QPushButton* GUI::Util::Layer::createAddParticleButton(
    QWidget* parentWidget, std::function<void(FormfactorsCatalog::Type t)> slotAddFormFactor,
    std::function<void(ParticlesCatalog::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 : FormfactorsCatalog::hardParticleTypes()) {
        const auto ui = FormfactorsCatalog::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 : FormfactorsCatalog::rippleTypes()) {
        const auto ui = FormfactorsCatalog::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 : {ParticlesCatalog::Type::Composition, ParticlesCatalog::Type::CoreShell,
                            ParticlesCatalog::Type::Mesocrystal}) {
        const auto ui = ParticlesCatalog::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;
}

QVector<QColor> GUI::Util::Layer::predefinedLayerColors()
{
    static QVector<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;
}
