//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Material/MaterialEditorDialog.cpp
//! @brief     Implements class MaterialEditorDialog.
//!
//! @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/MaterialEditorDialog.h"
#include "GUI/Model/Material/MaterialItem.h"
#include "GUI/Model/Material/MaterialsSet.h"
#include "GUI/Model/Sample/ItemWithMaterial.h"
#include "GUI/Model/Sample/SampleItem.h"
#include "GUI/View/Base/Fontsize.h"
#include "GUI/View/Material/MaterialsQModel.h"
#include "GUI/View/Numeric/DoubleSpinBox.h"
#include "GUI/View/Setup/ActionFactory.h"
#include "GUI/View/Widget/StyledToolbar.h"
#include "GUI/View/Widget/WidgetSettings.h"
#include <QAction>
#include <QColorDialog>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSettings>
#include <QTreeView>
#include <QVBoxLayout>

//! Dialog to select a material and also to edit the list of existing materials.
//! The dialog operates on a copy of the current materials. The original material store is only
//! updated if changes have been made and the dialog has been closed with OK.
//! Use this dialog only with the two static methods to edit the list, or to choose a material
class MaterialEditorDialog : public QDialog {
public:
    MaterialEditorDialog(SampleItem* sample, const QString& identifier);
    ~MaterialEditorDialog() override;

    MaterialItem* currentMaterialItem();

private:
    void addRefractiveMaterial();
    void addSldMaterial();
    void removeCurrentMaterial();

    void setCurrentMaterial(const MaterialItem* m);

    void selectColor();

    void onSelectColor();
    void onSelectMaterial();

    void onChangeCurrent();
    QModelIndex currentIndex() const;

    //! Returns the list of material identifiers of the materials currently used in the sample.
    //! E.g. the material selected in a particle.
    QStringList identifiersOfUsedMaterials() const;

    QAction* m_remove_material_action;

    MaterialsQModel* m_model;
    SampleItem* m_sample;

    QTreeView* m_tree_view;
    QPushButton* m_select_color_button;
    QWidget* m_editor;
    QGroupBox* m_sld_group;
    QGroupBox* m_refr_group;
    DoubleSpinBox* m_delta_spinbox;
    DoubleSpinBox* m_beta_spinbox;
    DoubleSpinBox* m_re_sld_spinbox;
    DoubleSpinBox* m_im_sld_spinbox;
    DoubleSpinBox* m_Bx_spinbox;
    DoubleSpinBox* m_By_spinbox;
    DoubleSpinBox* m_Bz_spinbox;
    QLineEdit* m_name_edit;
    QLineEdit* m_color_info;
};


MaterialEditorDialog::MaterialEditorDialog(SampleItem* sample, const QString& identifier)
    : m_sample(sample)
{
    setObjectName("MaterialEditorDialog");

    m_model = new MaterialsQModel(m_sample->materialModel());

    setGeometry(0, 0, 1023, 469);
    setWindowTitle("Material Editor");

    auto* layout = new QVBoxLayout;
    setLayout(layout);

    auto* hlayout = new QHBoxLayout;
    layout->addLayout(hlayout);

    //... Left side: list of materials

    m_tree_view = new QTreeView;
    hlayout->addWidget(m_tree_view);
    m_tree_view->setCurrentIndex(m_model->indexFromMaterial(identifier));
    m_tree_view->setContextMenuPolicy(Qt::ActionsContextMenu);
    m_tree_view->setSelectionMode(QAbstractItemView::SingleSelection);
    m_tree_view->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_tree_view->setRootIsDecorated(false);

    //... Right side: editor for one material

    m_editor = new QWidget;
    hlayout->addWidget(m_editor);
    auto* ed_vlayout = new QVBoxLayout;
    m_editor->setLayout(ed_vlayout);
    ed_vlayout->setSpacing(6);
    ed_vlayout->setContentsMargins(0, 0, 0, 0);

    // Name and color

    auto* nc_form = new QFormLayout;
    ed_vlayout->addLayout(nc_form);
    nc_form->setWidget(0, QFormLayout::LabelRole, new QLabel("Name:"));
    m_name_edit = new QLineEdit;
    nc_form->setWidget(0, QFormLayout::FieldRole, m_name_edit);

    nc_form->setWidget(1, QFormLayout::LabelRole, new QLabel("Color:"));
    auto* colorLine = new QHBoxLayout;
    nc_form->setLayout(1, QFormLayout::FieldRole, colorLine);

    m_select_color_button = new QPushButton;
    colorLine->addWidget(m_select_color_button);
    QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
    sizePolicy.setHorizontalStretch(0);
    sizePolicy.setVerticalStretch(0);
    sizePolicy.setHeightForWidth(m_select_color_button->sizePolicy().hasHeightForWidth());
    m_select_color_button->setSizePolicy(sizePolicy);
    m_select_color_button->setToolTip("Choose color");

    m_color_info = new QLineEdit;
    colorLine->addWidget(m_color_info);
    m_color_info->setEnabled(false);

    // Refractive data

    m_refr_group = new QGroupBox("Refractive index");
    ed_vlayout->addWidget(m_refr_group);

    auto* refr_form = new QFormLayout;
    m_refr_group->setLayout(refr_form);
    m_delta_spinbox = new DoubleSpinBox(nullptr);
    m_beta_spinbox = new DoubleSpinBox(nullptr);
    refr_form->addRow("Delta:", m_delta_spinbox);
    refr_form->addRow("Beta:", m_beta_spinbox);

    m_sld_group = new QGroupBox("Scattering length density (1/Å²)");
    ed_vlayout->addWidget(m_sld_group);

    auto* sld_form = new QFormLayout;
    m_sld_group->setLayout(sld_form);
    m_re_sld_spinbox = new DoubleSpinBox(nullptr);
    m_im_sld_spinbox = new DoubleSpinBox(nullptr);
    sld_form->addRow("Real:", m_re_sld_spinbox);
    sld_form->addRow("Imaginary:", m_im_sld_spinbox);

    // Magnetization

    auto* magn_group = new QGroupBox("Magnetization (A/m)");
    ed_vlayout->addWidget(magn_group);
    auto* magn_form = new QFormLayout(magn_group);

    m_Bx_spinbox = new DoubleSpinBox(nullptr);
    m_By_spinbox = new DoubleSpinBox(nullptr);
    m_Bz_spinbox = new DoubleSpinBox(nullptr);

    magn_form->addRow("X:", m_Bx_spinbox);
    magn_form->addRow("Y:", m_By_spinbox);
    magn_form->addRow("Z:", m_Bz_spinbox);

    //... Buttons at bottom

    auto* verticalSpacer = new QSpacerItem(20, 15, QSizePolicy::Minimum, QSizePolicy::Expanding);
    ed_vlayout->addItem(verticalSpacer);

    auto* m_button_box = new QDialogButtonBox;
    layout->addWidget(m_button_box);
    m_button_box->setOrientation(Qt::Horizontal);
    m_button_box->setStandardButtons(QDialogButtonBox::Ok);

    //... Configure

    // Setting z-component is temporary disabled (see issue #654)
    // When implemented, rm disabling
    m_Bz_spinbox->setDisabled(true);

    auto* addRefractiveMaterialAction = new QAction("Add material (refractive index)");
    addRefractiveMaterialAction->setIcon(QIcon(":/images/shape-square-plus.svg"));
    addRefractiveMaterialAction->setToolTip("Add new material");
    connect(addRefractiveMaterialAction, &QAction::triggered, this,
            &MaterialEditorDialog::addRefractiveMaterial);

    auto* addSldMaterialAction = new QAction("Add material (SLD)");
    addSldMaterialAction->setIcon(QIcon(":/images/shape-square-plus.svg"));
    addSldMaterialAction->setToolTip("Add new material");
    connect(addSldMaterialAction, &QAction::triggered, this, &MaterialEditorDialog::addSldMaterial);

    m_remove_material_action = ActionFactory::createRemoveAction("material");
    connect(m_remove_material_action, &QAction::triggered, this,
            &MaterialEditorDialog::removeCurrentMaterial);

    m_tree_view->addAction(addRefractiveMaterialAction);
    m_tree_view->addAction(addSldMaterialAction);
    auto* separator = new QAction;
    separator->setSeparator(true);
    m_tree_view->addAction(separator);
    m_tree_view->addAction(m_remove_material_action);
    m_tree_view->setModel(m_model);
    m_tree_view->header()->setSectionResizeMode(QHeaderView::ResizeToContents);

    auto* toolbar = new StyledToolbar;
    toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    toolbar->addAction(addRefractiveMaterialAction);
    toolbar->addAction(addSldMaterialAction);
    toolbar->addAction(m_remove_material_action);
    layout->insertWidget(0, toolbar);

    GUI::Style::setResizable(this);
    GUI::WidgetSettings::load(this);

    connect(m_tree_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
            &MaterialEditorDialog::onChangeCurrent);

    connect(m_select_color_button, &QPushButton::clicked, this, &MaterialEditorDialog::selectColor);

    connect(m_name_edit, &QLineEdit::textEdited,
            [&](const QString& t) { m_model->setMaterialItemName(currentIndex(), t); });

    connect(m_button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
    connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);

    if (m_model->rowCount() > 0)
        m_tree_view->setCurrentIndex(m_model->first());
    else {
        m_sld_group->hide();
        m_editor->setEnabled(false);
    }

    onSelectMaterial();
}

MaterialEditorDialog::~MaterialEditorDialog()
{
    GUI::WidgetSettings::save(this);
}

void MaterialEditorDialog::addRefractiveMaterial()
{
    setCurrentMaterial(m_model->addRefractiveMaterialItem("unnamed", 0.0, 0.0));
}

void MaterialEditorDialog::addSldMaterial()
{
    setCurrentMaterial(m_model->addSLDMaterialItem("unnamed", 0.0, 0.0));
}

void MaterialEditorDialog::removeCurrentMaterial()
{
    MaterialItem* material = currentMaterialItem();
    if (!material)
        return;

    if (identifiersOfUsedMaterials().contains(material->identifier())) {
        QMessageBox::warning(this, "Remove material",
                             "This material cannot be removed - it is used in the current sample.");
        return;
    }

    m_model->removeMaterial(currentIndex());
}

void MaterialEditorDialog::selectColor()
{
    if (auto newColor = QColorDialog::getColor(currentMaterialItem()->color()); newColor.isValid())
        m_model->setColor(currentIndex(), newColor);
    onSelectColor();
}

void MaterialEditorDialog::onSelectColor()
{
    auto* materialItem = currentMaterialItem();
    m_name_edit->setText(materialItem->matItemName());
    m_color_info->setText(QString("[%1, %2, %3] (%4)")
                              .arg(materialItem->color().red())
                              .arg(materialItem->color().green())
                              .arg(materialItem->color().blue())
                              .arg(materialItem->color().alpha()));
    QPixmap pixmap(m_select_color_button->iconSize());
    pixmap.fill(materialItem->color());
    m_select_color_button->setIcon(pixmap);
}

void MaterialEditorDialog::onSelectMaterial()
{
    MaterialItem* materialItem = currentMaterialItem();
    if (!materialItem)
        return;

    m_refr_group->setVisible(materialItem->hasRefractiveIndex());
    m_sld_group->setVisible(!materialItem->hasRefractiveIndex());

    m_editor->setEnabled(materialItem != nullptr);
    if (materialItem == nullptr) {
        m_refr_group->show();
        m_sld_group->hide();
        for (auto* lineEdit : m_editor->findChildren<QLineEdit*>())
            lineEdit->clear();
        for (auto* spinBox : m_editor->findChildren<DoubleSpinBox*>())
            spinBox->replaceProperty(nullptr);
        return;
    }

    m_delta_spinbox->replaceProperty(&materialItem->delta());
    m_beta_spinbox->replaceProperty(&materialItem->beta());
    m_re_sld_spinbox->replaceProperty(&materialItem->sldRe());
    m_im_sld_spinbox->replaceProperty(&materialItem->sldIm());

    m_Bx_spinbox->replaceProperty(&materialItem->magnetization().x());
    m_By_spinbox->replaceProperty(&materialItem->magnetization().y());
    m_Bz_spinbox->replaceProperty(&materialItem->magnetization().z());
}

void MaterialEditorDialog::onChangeCurrent()
{
    m_remove_material_action->setEnabled(currentIndex().isValid());

    onSelectMaterial();
    onSelectColor();
}

MaterialItem* MaterialEditorDialog::currentMaterialItem()
{
    return currentIndex().isValid() ? m_model->materialItemFromIndex(currentIndex()) : nullptr;
}

void MaterialEditorDialog::setCurrentMaterial(const MaterialItem* m)
{
    m_tree_view->setCurrentIndex(m_model->indexFromMaterial(m));
    onSelectMaterial();
}

QModelIndex MaterialEditorDialog::currentIndex() const
{
    return m_tree_view->currentIndex();
}

QStringList MaterialEditorDialog::identifiersOfUsedMaterials() const
{
    QStringList result;
    for (auto* p : m_sample->itemsWithMaterial())
        result << p->materialIdentifier();
    return result;
}


//! Static caller

QString GUI::chooseMaterial(SampleItem* sample, const QString& identifierOfPreviousMaterial)
{
    MaterialEditorDialog dialog(sample, identifierOfPreviousMaterial);
    if (dialog.exec() == QDialog::Accepted)
        if (MaterialItem* material = dialog.currentMaterialItem())
            return material->identifier();

    return {};
}
