//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Instrument/DistributionEditor.cpp
//! @brief     Implements classes DistributionSelector and DistributionEditor
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "GUI/View/Instrument/DistributionEditor.h"
#include "GUI/Model/Beam/BeamDistributionItem.h"
#include "GUI/Model/Descriptor/DistributionItems.h"
#include "GUI/Support/XML/Backup.h"
#include "GUI/View/Instrument/DistributionPlot.h"
#include "GUI/View/Numeric/DoubleSpinBox.h"
#include "GUI/View/Numeric/NumWidgetUtil.h"
#include "GUI/View/Numeric/ScientificSpinBox.h"
#include "GUI/View/Tool/GroupBoxCollapser.h"

//==================================================================================================
// DistributionSelector
//==================================================================================================

DistributionSelector::DistributionSelector(std::optional<MeanConfig> mean_config,
                                           GUI::ID::Distributions distributions, QWidget* parent,
                                           BeamDistributionItem* item, bool allow_distr)
    : QWidget(parent)
    , m_item(item)
    , m_meanConfig(std::move(mean_config))
    , m_distributions(distributions)
{
    ASSERT(item);
    m_formLayout = new QFormLayout(this);
    m_formLayout->setContentsMargins(0, 0, 0, 0);

    m_distributionCombo =
        GUI::Util::createComboBoxFromProperty(item->distributionSelection(), [this](int) {
            createDistributionWidgets();
            emit distributionChanged();
        });
    m_distributionCombo->setEnabled(allow_distr);
    m_formLayout->addRow("Distribution:", m_distributionCombo);

    createDistributionWidgets();
}

void DistributionSelector::createDistributionWidgets()
{
    while (m_formLayout->rowCount() > 1)
        m_formLayout->removeRow(1);

    if (auto* it = dynamic_cast<DistributionCosineItem*>(m_item->distributionItem())) {
        createMeanSpinBox(it->mean());
        createSpinBox(it->hwhm());
        createNumSamplesSpinBox(it);
    } else if (auto* it = dynamic_cast<DistributionGateItem*>(m_item->distributionItem())) {
        auto* minSpinbox = createSpinBox(it->minimum());
        auto* maxSpinbox = createSpinBox(it->maximum());
        connect(minSpinbox, &DoubleSpinBox::baseValueChanged, [it, maxSpinbox](double d) {
            if (d > it->maximum()) {
                it->setMaximum(d);
                maxSpinbox->updateValue();
            }
        });
        connect(maxSpinbox, &DoubleSpinBox::baseValueChanged, [it, minSpinbox](double d) {
            if (d < it->minimum()) {
                it->setMinimum(d);
                minSpinbox->updateValue();
            }
        });
        createNumSamplesSpinBox(it);
    } else if (auto* it = dynamic_cast<DistributionGaussianItem*>(m_item->distributionItem())) {
        createMeanSpinBox(it->mean());
        createSpinBox(it->standardDeviation());
        createNumSamplesSpinBox(it);
        createSpinBox(it->relSamplingWidth());
    } else if (auto* it = dynamic_cast<DistributionLogNormalItem*>(m_item->distributionItem())) {
        createSpinBox(it->median());
        createSpinBox(it->scaleParameter());
        createNumSamplesSpinBox(it);
        createSpinBox(it->relSamplingWidth());
    } else if (auto* it = dynamic_cast<DistributionLorentzItem*>(m_item->distributionItem())) {
        createMeanSpinBox(it->mean());
        createSpinBox(it->hwhm());
        createNumSamplesSpinBox(it);
        createSpinBox(it->relSamplingWidth());
    } else if (auto* it = dynamic_cast<DistributionNoneItem*>(m_item->distributionItem())) {
        createMeanSpinBox(it->mean());
    } else if (auto* it = dynamic_cast<DistributionTrapezoidItem*>(m_item->distributionItem())) {
        createSpinBox(it->center());
        createSpinBox(it->leftWidth());
        createSpinBox(it->middleWidth());
        createSpinBox(it->rightWidth());
        createNumSamplesSpinBox(it);
    }
}

void DistributionSelector::createNumSamplesSpinBox(DistributionItem* dist)
{
    ASSERT(dist);
    m_formLayout->addRow("Number of samples:",
                         GUI::Util::createIntSpinbox([dist] { return dist->numberOfSamples(); },
                                                     [this, dist](int v) {
                                                         dist->setNumberOfSamples(v);
                                                         emit distributionChanged();
                                                     },
                                                     RealLimits::lowerLimited(1)));
}

DoubleSpinBox* DistributionSelector::createSpinBox(DoubleProperty& d)
{
    auto* sb = GUI::Util::createDoubleSpinBoxRow(m_formLayout, d);
    connect(sb, &DoubleSpinBox::baseValueChanged, [this, &d](double v) {
        d.setValue(v);
        emit distributionChanged();
    });
    return sb;
}

void DistributionSelector::createMeanSpinBox(DoubleProperty& d)
{
    if (m_meanConfig) {
        if (m_meanConfig->scientific) {
            auto* sb = GUI::Util::createScientificSpinBox(m_formLayout, d);
            connect(sb, &ScientificSpinBox::valueChanged, [this, &d](double v) {
                d.setValue(v);
                emit distributionChanged();
            });
        } else
            createSpinBox(d);
    }
}

BeamDistributionItem* DistributionSelector::item() const
{
    return m_item;
}

GUI::ID::Distributions DistributionSelector::distributions() const
{
    return m_distributions;
}

void DistributionSelector::refresh()
{
    QSignalBlocker b(m_distributionCombo);
    m_distributionCombo->setCurrentIndex(m_item->distributionSelection().currentIndex());
    createDistributionWidgets();
}


//==================================================================================================
// DistributionEditor
//==================================================================================================

DistributionEditor::DistributionEditor(const QString& title,
                                       const std::optional<MeanConfig>& mean_config,
                                       GUI::ID::Distributions distributions, QWidget* parent,
                                       BeamDistributionItem* item, bool allow_distr)
    : QGroupBox(title, parent)
{
    auto* hLayout = new QHBoxLayout(this);
    m_selector = new DistributionSelector(mean_config, distributions, this, item, allow_distr);
    hLayout->addWidget(m_selector);
    hLayout->setSpacing(50);

    m_plot = new DistributionPlot(this);
    m_plot->setFixedSize(280, 170);
    m_plot->setShowMouseCoords(false);
    hLayout->addWidget(m_plot);
    hLayout->addStretch(1);

    auto* collapser = GroupBoxCollapser::installIntoGroupBox(this);
    collapser->setExpanded(item->isExpandGroupBox());
    connect(collapser, &GroupBoxCollapser::toggled, this,
            [item](bool b) { item->setExpandGroupBox(b); });

    connect(m_selector, &DistributionSelector::distributionChanged, this,
            &DistributionEditor::distributionChanged);
    connect(m_selector, &DistributionSelector::distributionChanged, this,
            &DistributionEditor::updatePlot);

    updatePlot();
}

void DistributionEditor::updateData()
{
    m_selector->refresh();
}

void DistributionEditor::updatePlot()
{
    auto* d = m_selector->item()->distributionItem();
    m_plot->setVisible(!dynamic_cast<const DistributionNoneItem*>(d));
    m_plot->setDistItem(d);
    m_plot->plotItem();
}
