//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Device/ScanEditor.cpp
//! @brief     Defines class ScanEditor.
//!
//! @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/Device/ScanEditor.h"
#include "Base/Axis/Frame.h"
#include "Base/Const/Units.h"
#include "GUI/Model/Axis/PointwiseAxisItem.h"
#include "GUI/Model/Beam/BeamDistributionItem.h"
#include "GUI/Model/Beam/SourceItems.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Model/Sim/InstrumentsSet.h"
#include "GUI/View/Base/LayoutUtil.h"
#include "GUI/View/Device/AxisScanEditor.h"
#include "GUI/View/Device/DistributionEditor.h"
#include "GUI/View/Device/FootprintForm.h"
#include "GUI/View/Numeric/ComboUtil.h"
#include "GUI/View/Numeric/DSpinBox.h"
#include "GUI/View/Numeric/NumWidgetUtil.h"

ScanEditor::ScanEditor(QWidget* parent, InstrumentItem* instr_item, ScanItem* item, bool allow_phi,
                       bool allow_footprint, bool allow_distr)
    : CollapsibleGroupBox("Beam and scan parameters", parent, item->expandBeamParameters)
    , m_scan_item(item)
    , m_instr_item(instr_item)
{
    auto* layout = new QGridLayout(body());
    layout->setAlignment(Qt::AlignTop);

    //... Scan type

    auto* scan_form = new QFormLayout;
    scan_form->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint);
    layout->addLayout(scan_form, 0, 0);
    ASSERT(item->scanTypeSelection().certainItem());
    auto* typeCombo = GUI::Util::createComboBoxFromPolyPtr(
        item->scanTypeSelection(),
        [this, allow_phi, allow_distr](int) { updateScanWidgets(allow_phi, allow_distr); }, true);
    scan_form->addRow("Scan type:", typeCombo);

    //... Intensity

    auto* form = new QFormLayout;
    layout->addLayout(form, 1, 0);
    form->setFieldGrowthPolicy(QFormLayout::FieldsStayAtSizeHint);
    auto* intensity_spinbox = GUI::Util::addDoubleSpinBoxRow(form, item->intensity());
    intensity_spinbox->setToolTip("Correction factor for normalized intensity");

    //... Scan unit (deg vs 1/nm)
    // TODO

    m_beam_layout = new QHBoxLayout;
    layout->addLayout(m_beam_layout, 2, 0);

    //... Footprint

    if (allow_footprint) {
        auto* footprintEditor = new FootprintForm(this, item);
        if (allow_phi)
            layout->addWidget(footprintEditor, 3, 0, 1, 3);
        else
            layout->addWidget(footprintEditor, 2, 2);
    }

    updateScanWidgets(allow_phi, allow_distr);
}

void ScanEditor::updateScanWidgets(bool allow_phi, bool allow_distr)
{
    GUI::Util::Layout::clearLayout(m_beam_layout);

    ScanTypeItem* type = m_scan_item->scanTypeSelection().certainItem();
    DistributionEditor* beamDistributionEditor = nullptr;
    AxisScanEditor* scanEditor = nullptr;

    if (dynamic_cast<const AlphaScanTypeItem*>(type)) {

        //... Wavelength

        beamDistributionEditor = new DistributionEditor(
            "Wavelength (nm)", true, DistributionSelector::Category::Symmetric, this,
            m_scan_item->wavelengthItem(), allow_distr);
        m_beam_layout->addWidget(beamDistributionEditor);

        //... Inclination scan

        scanEditor = new AxisScanEditor(this, m_scan_item, "Grazing angles", "deg", allow_distr);
        m_scan_item->scanDistributionItem()->setScaleFactor(Units::deg);
        m_beam_layout->addWidget(scanEditor);

    } else if (dynamic_cast<const LambdaScanTypeItem*>(type)) {

        //... Wavelength scan

        scanEditor = new AxisScanEditor(this, m_scan_item, "Wavelengths", "nm", allow_distr);
        m_scan_item->scanDistributionItem()->setScaleFactor(1.);
        m_beam_layout->addWidget(scanEditor);

        //... Inclination

        beamDistributionEditor = new DistributionEditor(
            "Grazing angle (deg)", true, DistributionSelector::Category::Symmetric, this,
            m_scan_item->grazingAngleItem(), allow_distr);
        m_beam_layout->addWidget(beamDistributionEditor);
    } else
        ASSERT_NEVER;

    //... Azimuthal angle

    if (allow_phi) {
        auto* azimuthalEditor = new DistributionEditor("Azimuthal angle (deg)", true,
                                                       DistributionSelector::Category::All, this,
                                                       m_scan_item->azimuthalAngleItem());
        m_beam_layout->addWidget(azimuthalEditor);
    }

    // Couple beam distribution and scan. Indicators have to be recalculated first, then updated.
    ASSERT(beamDistributionEditor);
    ASSERT(scanEditor);
    connect(beamDistributionEditor, &DistributionEditor::distributionChanged, [this, scanEditor] {
        // Update values in pointwise axis after a change of the distribution mean.
        if (auto* iiI = dynamic_cast<const SpecularInstrumentItem*>(m_instr_item))
            if (auto* pw = dynamic_cast<PointwiseAxisItem*>(iiI->scanItem()->currentAxisItem()))
                pw->updateAxIndicators(m_instr_item->makeFrame());

        emit scanEditor->updateIndicators();
        emit gDoc->setModified();
    });
    beamDistributionEditor->distributionChanged();
}
