//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Setup/AxesPanel.cpp
//! @brief     Implements class AxesPanel.
//!
//! @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/Setup/AxesPanel.h"
#include "Base/Math/Numeric.h"
#include "Base/Util/Assert.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Data/Data2DItem.h"
#include "GUI/Model/File/DatafilesSet.h"
#include "GUI/Model/Job/JobsSet.h"
#include "GUI/Model/Project/DataSource.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Base/LayoutUtil.h"
#include "GUI/View/Numeric/DSpinBox.h"
#include "GUI/View/Numeric/NumWidgetUtil.h"
#include "GUI/View/Widget/AppConfig.h"
#include "GUI/View/Widget/GroupBoxes.h"
#include <QCheckBox>
#include <QLineEdit>

AxesPanel::AxesPanel(const DataSource* ds)
    : m_data_source(ds)
{
    setWindowTitle("Properties");
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding);

    m_editor_layout = new QFormLayout(this);
    m_editor_layout->setContentsMargins(8, 20, 8, 8);
    m_editor_layout->setSpacing(5);

    // subscription type depends on the context
    if (m_data_source->isFromData())
        connect(gDoc->datafiles(), &DatafilesSet::setChanged, this, &AxesPanel::updatePanel);
    else
        connect(gDoc->jobs(), &JobsSet::jobPlotContextChanged, this, &AxesPanel::updatePanel);
}

AxesPanel::~AxesPanel() = default;

void AxesPanel::updatePanel()
{
    if (!d2Item())
        return;

    updateAxesProperties();

    GUI::Util::Layout::clearLayout(m_editor_layout);

    m_editor_layout->addRow(GUI::Util::createCheckBox(
        "Interpolate", [this] { return d2Item()->isInterpolated(); },
        [this](bool b) {
            for (auto* item : m_data_source->allData2DItems())
                item->setInterpolated(b);
            gDoc->setModified();
        }));

    // -- x-axis
    auto* xGroup = new StaticGroupBox("X axis", this);
    auto* xFormLayout = new QFormLayout(xGroup->body());
    xFormLayout->setContentsMargins(0, 0, 0, 0);
    xFormLayout->setSpacing(5);

    auto* x_min_sb = GUI::Util::addDoubleSpinBoxRow(xFormLayout, d2Item()->axItemX()->min());
    connect(x_min_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->allData2DItems())
            item->axItemX()->setMin(newValue);
    });

    auto* x_max_sb = GUI::Util::addDoubleSpinBoxRow(xFormLayout, d2Item()->axItemX()->max());
    connect(x_max_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->allData2DItems())
            item->axItemX()->setMax(newValue);
    });

    m_editor_layout->addRow(xGroup);

    // -- y-axis
    auto* yGroup = new StaticGroupBox("Y axis", this);
    auto* yFormLayout = new QFormLayout(yGroup->body());
    yFormLayout->setContentsMargins(0, 0, 0, 0);
    yFormLayout->setSpacing(5);

    auto* y_min_sb = GUI::Util::addDoubleSpinBoxRow(yFormLayout, d2Item()->axItemY()->min());
    connect(y_min_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->allData2DItems())
            item->axItemY()->setMin(newValue);
    });

    auto* y_max_sb = GUI::Util::addDoubleSpinBoxRow(yFormLayout, d2Item()->axItemY()->max());
    connect(y_max_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->allData2DItems())
            item->axItemY()->setMax(newValue);
    });

    m_editor_layout->addRow(yGroup);

    // -- color-axis
    auto* zGroup = new StaticGroupBox("Color legend", this);
    auto* zFormLayout = new QFormLayout(zGroup->body());
    zFormLayout->setContentsMargins(0, 0, 0, 0);
    zFormLayout->setSpacing(5);

    auto* z_min_sb = GUI::Util::addDoubleSpinBoxRow(zFormLayout, d2Item()->zAxisItem()->min());
    connect(z_min_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->allData2DItems()) {
            item->zAxisItem()->setMin(newValue);
            item->zAxisItem()->adjustLogRangeOrders();
        }
    });

    auto* z_max_sb = GUI::Util::addDoubleSpinBoxRow(zFormLayout, d2Item()->zAxisItem()->max());
    connect(z_max_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->mainData2DItems()) {
            item->zAxisItem()->setMax(newValue);
            item->zAxisItem()->adjustLogRangeOrders();
        }
    });

    auto* logRange_sb = new DSpinBox(&d2Item()->zAxisItem()->logRangeOrders());
    connect(logRange_sb, &DSpinBox::valueChanged, [this](double newValue) {
        for (auto* item : m_data_source->mainData2DItems())
            item->zAxisItem()->setLogRangeOrders(newValue);
    });

    zFormLayout->addRow(GUI::Util::createCheckBox(
        "log10", [this] { return d2Item()->zAxisItem()->isLogScale(); },
        [this, logRange_sb](bool b) {
            logRange_sb->setEnabled(b);
            for (auto* item : m_data_source->allData2DItems())
                item->zAxisItem()->setLogScale(b);
        }));

    zFormLayout->addRow(d2Item()->zAxisItem()->logRangeOrders().label() + ":", logRange_sb);

    zFormLayout->addRow(GUI::Util::createCheckBox(
        "Visible", [this] { return d2Item()->zAxisItem()->isVisible(); },
        [this](bool b) {
            for (auto* item : m_data_source->allData2DItems())
                item->zAxisItem()->setVisible(b);
        }));

    m_editor_layout->addRow(zGroup);
}

void AxesPanel::updateAxesProperties()
{
    ASSERT(d2Item());

    const double step_factor = 0.005;
    const int start_decimals = 4;

    // x axis
    auto* aX = d2Item()->axItemX();
    const double x_range = std::abs(aX->max().dVal() - aX->min().dVal());
    const int x_decimals = start_decimals - Numeric::orderOfMagnitude(x_range);
    const double x_step = x_range * step_factor;

    aX->min().setStepAndDecimals(true, x_step, x_decimals);
    aX->max().setStepAndDecimals(true, x_step, x_decimals);

    // y axis
    auto* aY = d2Item()->axItemY();
    const double y_range = std::abs(aY->max().dVal() - aY->min().dVal());
    const int y_decimals = start_decimals - Numeric::orderOfMagnitude(y_range);
    const double y_step = y_range * step_factor;

    aY->min().setStepAndDecimals(true, y_step, y_decimals);
    aY->max().setStepAndDecimals(true, y_step, y_decimals);
}

//  ************************************************************************************************
//  shortcuts
//  ************************************************************************************************

Data2DItem* AxesPanel::d2Item()
{
    return m_data_source->currentData2DItem();
}
