//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Setup/MasksPanel.cpp
//! @brief     Implements class MasksPanel.
//!
//! @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/MasksPanel.h"
#include "Base/Math/Numeric.h"
#include "Fit/Param/RealLimits.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/Mask/MasksSet.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Base/LayoutUtil.h"
#include "GUI/View/Modelview/SetView.h"
#include "GUI/View/Numeric/DSpinBox.h"
#include "GUI/View/Numeric/NumWidgetUtil.h"
#include "GUI/View/Widget/GroupBoxes.h"
#include <QCheckBox>
#include <QLineEdit>

namespace {

void addSpinBox(MaskItem* mask, QFormLayout* layout, DoubleProperty& property)
{
    auto* spinbox = GUI::Util::addDoubleSpinBoxRow(layout, property);
    QObject::connect(spinbox, &DSpinBox::valueChanged, [mask](double) {
        // set dynamic limits for rectangle
        if (auto* rect = dynamic_cast<RectangleItem*>(mask)) {
            rect->xLow().setLimits(RealLimits::upperLimited(rect->xUp().dVal()));
            rect->xUp().setLimits(RealLimits::lowerLimited(rect->xLow().dVal()));
            rect->yLow().setLimits(RealLimits::upperLimited(rect->yUp().dVal()));
            rect->yUp().setLimits(RealLimits::lowerLimited(rect->yLow().dVal()));
        }
        ASSERT(mask);
        emit mask->maskGeometryChanged();
    });
    QObject::connect(&property, &DoubleProperty::setAndNotifyCalled, spinbox,
                     &DSpinBox::updateValue);
}

} // namespace


MasksPanel::MasksPanel(const std::function<MasksSet*()>& set_source, bool data_not_job,
                       bool mask_not_prjn)
    : m_set_source(set_source)
    , m_set_view(new SetView(nullptr))
    , m_data_not_job(data_not_job)
    , m_mask_not_prjn(mask_not_prjn)
{
    auto* layout = new QVBoxLayout(this);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(8);

    // -- mask stack
    auto* header_line = new QWidget(this);
    layout->addWidget(header_line);
    auto* header_layout = new QHBoxLayout(header_line);

    auto* rm_action = new QAction("Delete current item");
    rm_action->setIcon(QIcon(":/images/delete.svg"));
    rm_action->setToolTip("Delete current item");

    auto* rm_button = new QToolButton;
    rm_button->setDefaultAction(rm_action);
    header_layout->addWidget(rm_button);

    layout->addWidget(m_set_view);

    // -- mask properties
    auto* g3 = new QWidget(this);
    g3->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
    layout->addWidget(g3);
    m_editor_layout = new QFormLayout(g3);
    m_editor_layout->setContentsMargins(0, 0, 0, 0);

    connect(gDoc->datafiles(), &DatafilesSet::setChanged, this, &MasksPanel::updateMasksPanel);
    connect(gDoc->jobs(), &JobsSet::jobPlotContextChanged, this, &MasksPanel::updateMasksPanel);

    connect(rm_action, &QAction::triggered, this, &MasksPanel::deleteCurrentRequest);
}

void MasksPanel::updateMasksPanel()
{
    m_set_view->setSet(m_set_source());
    if (m_set_source())
        connect(m_set_source(), &MasksSet::setChanged, this, &MasksPanel::updateMaskEditor,
                Qt::UniqueConnection);
    updateMaskEditor();
}

//! Private function, called by different functions.
//! Overwrites m_editor_layout.
void MasksPanel::updateMaskEditor()
{
    GUI::Util::Layout::clearLayout(m_editor_layout);

    MaskItem* t = m_set_source() ? m_set_source()->currentItem() : nullptr;
    if (!t)
        return;

    updateMaskPresentationProperties(t);

    if (m_mask_not_prjn) {
        // -- mask value
        if (!dynamic_cast<RegionOfInterestItem*>(t)) {
            auto* check_box = new QCheckBox("Mask value", m_editor_layout->parentWidget());
            m_editor_layout->addRow(check_box);
            check_box->setChecked(t->maskValue());
            connect(check_box, &QCheckBox::stateChanged, [t, check_box] {
                ASSERT(check_box);
                ASSERT(t);
                t->setMaskValue(check_box->isChecked());
                emit t->maskGeometryChanged();
                gDoc->setModified();
            });
        }

        // -- mask visibility
        auto* check_box = new QCheckBox("Show", m_editor_layout->parentWidget());
        m_editor_layout->addRow(check_box);
        check_box->setChecked(t->isVisible());
        connect(check_box, &QCheckBox::stateChanged, [this, t, check_box] {
            ASSERT(check_box);
            ASSERT(t);
            t->setIsVisible(check_box->isChecked());
            t->setWasVisible(check_box->isChecked());
            emit m_set_source() -> setChanged(); // reselect the overlay of the current mask item
            emit t->maskGeometryChanged();
            gDoc->setModified();
        });
    }

    // -- name (only if not RoI)
    if (!dynamic_cast<RegionOfInterestItem*>(t)) {
        auto* edit = new QLineEdit(t->name(), m_editor_layout->parentWidget());
        connect(edit, &QLineEdit::textEdited, [t](const QString& s) { t->setName(s); });
        connect(edit, &QLineEdit::editingFinished, [] { gDoc->setModified(); });
        m_editor_layout->addRow("Name:", edit);
    }

    if (auto* c = dynamic_cast<RectangleItem*>(t)) {
        addSpinBox(t, m_editor_layout, c->xLow());
        addSpinBox(t, m_editor_layout, c->xUp());
        addSpinBox(t, m_editor_layout, c->yLow());
        addSpinBox(t, m_editor_layout, c->yUp());
    } else if (auto* c = dynamic_cast<EllipseItem*>(t)) {
        addSpinBox(t, m_editor_layout, c->xCenter());
        addSpinBox(t, m_editor_layout, c->yCenter());
        addSpinBox(t, m_editor_layout, c->xRadius());
        addSpinBox(t, m_editor_layout, c->yRadius());
        // addSpinBox(t, m_editor_layout, c->angle());
    } else if (auto* c = dynamic_cast<LineItem*>(t))
        addSpinBox(t, m_editor_layout, c->pos());
}

void MasksPanel::updateMaskPresentationProperties(MaskItem* t)
{
    Data2DItem* data2D = nullptr;
    if (m_data_not_job)
        data2D = gDoc->datafilesRW()->currentItem()->data2DItem();
    else
        data2D = gDoc->jobsRW()->currentItem()->data2DItem();
    ASSERT(data2D);

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

    // x axis
    const auto* aX = data2D->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;

    // y axis
    const auto* aY = data2D->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;

    if (auto* c = dynamic_cast<RectangleItem*>(t)) {
        c->xLow().setStepAndDecimals(true, x_step, x_decimals);
        c->xUp().setStepAndDecimals(true, x_step, x_decimals);
        c->yLow().setStepAndDecimals(true, y_step, y_decimals);
        c->yUp().setStepAndDecimals(true, y_step, y_decimals);
    } else if (auto* c = dynamic_cast<EllipseItem*>(t)) {
        c->xCenter().setStepAndDecimals(true, x_step, x_decimals);
        c->xRadius().setStepAndDecimals(true, x_step, x_decimals);
        c->yCenter().setStepAndDecimals(true, y_step, y_decimals);
        c->yRadius().setStepAndDecimals(true, y_step, y_decimals);
    } else if (auto* c = dynamic_cast<HorizontalLineItem*>(t))
        c->pos().setStepAndDecimals(true, x_step, x_decimals);
    else if (auto* c = dynamic_cast<VerticalLineItem*>(t))
        c->pos().setStepAndDecimals(true, y_step, y_decimals);
}
