//  ************************************************************************************************
//
//  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_footprint, allow_distr](int) {
            updateScanWidgets(allow_phi, allow_footprint, allow_distr);
        },
        true);
    scan_form->addRow("Scan type:", typeCombo);

    // Hide non-physical scans (qz scan) from Offspecular instrument combo.
    if (dynamic_cast<const OffspecInstrumentItem*>(m_instr_item))
        for (int i = typeCombo->count() - 1; i >= 0; i--)
            for (const auto type : ScanTypeCatalog::nonPhysicalTypes())
                if (typeCombo->itemText(i) == ScanTypeCatalog::uiInfo(type).menuEntry)
                    typeCombo->removeItem(i);

    //... 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);

    updateScanWidgets(allow_phi, allow_footprint, allow_distr);
}

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

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

    auto upd_indicators = [this](AxisScanEditor* scanEditor) {
        ASSERT(scanEditor);
        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());

        scanEditor->updateIndicators();
        gDoc->setModified();
    };

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

        //... Qz scan

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

        upd_indicators(scanEditor);

        return; // Important to leave the method and not create forms related to physical scans!

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

        //... Wavelength

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

        //... Grazing angle 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);

        //... Grazing angle

        beamDistributionEditor =
            new DistributionEditor("Grazing angle", true, DistributionSelector::Category::All, 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", true, DistributionSelector::Category::All,
                                   this, m_scan_item->azimuthalAngleItem(), allow_distr);
        m_beam_layout->addWidget(azimuthalEditor);
    }

    //... Footprint

    if (allow_footprint) {
        auto* footprintEditor = new FootprintForm(this, m_scan_item);
        m_beam_layout->addWidget(footprintEditor);
    }

    // Couple beam distribution and scan. Indicators have to be recalculated first, then updated.
    ASSERT(beamDistributionEditor);
    ASSERT(scanEditor);
    connect(beamDistributionEditor, &DistributionEditor::distributionChanged,
            [scanEditor, upd_indicators] {
                // Update values in pointwise axis after a change of the distribution mean.
                upd_indicators(scanEditor);
            });

    upd_indicators(scanEditor);
}
