//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Frame/Fit1DFrame.cpp
//! @brief     Implements class Fit1DFrame.
//!
//! @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/Frame/Fit1DFrame.h"
#include "Base/Util/Assert.h"
#include "Device/Data/Datafield.h"
#include "Device/IO/DiffUtil.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Axis/BasicAxisItem.h"
#include "GUI/Model/Data/Data1DItem.h"
#include "GUI/Model/Data/RangeUtil.h"
#include "GUI/Model/Job/JobsSet.h"
#include "GUI/Model/Project/DataSource.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Canvas/ProgressCanvas.h"
#include "GUI/View/Canvas/SpecularPlotCanvas.h"
#include "GUI/View/Plotter/PlotStatusLabel.h"
#include "GUI/View/Plotter/SpecularPlot.h"
#include "GUI/View/Setup/AxisPanel.h"
#include "GUI/View/Setup/FrameActions.h"

Fit1DFrame::Fit1DFrame()
    : m_data_canvas(new SpecularPlotCanvas)
    , m_diff_canvas(new SpecularPlotCanvas)
    , m_status_label(new PlotStatusLabel(nullptr))
    , m_reset_view_action(new QAction(this))
{
    auto* vlayout = new QVBoxLayout;
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->setSpacing(0);

    auto* gridLayout = new QGridLayout;
    gridLayout->setContentsMargins(0, 0, 0, 0);
    gridLayout->setSpacing(0);

    gridLayout->addWidget(m_data_canvas, 0, 0, 1, -1);
    gridLayout->addWidget(m_diff_canvas, 1, 0, 1, 2);
    auto* progress_canvas = new ProgressCanvas;
    gridLayout->addWidget(progress_canvas, 1, 2, 1, 1);

    vlayout->addLayout(gridLayout);
    vlayout->addWidget(m_status_label);

    m_status_label->addPlot(m_data_canvas->specularPlot());
    m_status_label->addPlot(m_diff_canvas->specularPlot());

    auto* hlayout = new QHBoxLayout(this);
    hlayout->setContentsMargins(0, 0, 0, 0);
    hlayout->setSpacing(0);
    hlayout->addLayout(vlayout);

    m_data_source = std::make_unique<DataFromJob>();
    auto* axis_panel = new AxisPanel(m_data_source.get());
    hlayout->addWidget(axis_panel);
    axis_panel->setVisible(false);
    QObject::connect(gActions->toggle_properties_panel, &QAction::triggered, axis_panel,
                     &QWidget::setVisible);

    m_reset_view_action->setText("Center view");
    m_reset_view_action->setIcon(QIcon(":/images/camera-metering-center.svg"));
    m_reset_view_action->setToolTip("Reset View");
    connect(m_reset_view_action, &QAction::triggered, this, &Fit1DFrame::onResetViewAction);

    GUI::Util::Ranges::setCommonRangeY(m_data_source->mainData1DItems());

    connect(gDoc->jobs(), &JobsSet::setChanged, this, &Fit1DFrame::updateFrame);

    updateFrame();
}

Fit1DFrame::~Fit1DFrame() = default;

void Fit1DFrame::onResetViewAction()
{
    ASSERT(m_data_source->simuData1DItem() && m_data_source->diffData1DItem()
           && m_data_source->realData1DItem());
    m_data_source->simuData1DItem()->resetView();
    m_data_source->realData1DItem()->resetView();
    m_data_source->diffData1DItem()->resetView();

    // synchronize data range between simulated and real
    GUI::Util::Ranges::setCommonRangeY(m_data_source->mainData1DItems());
    gDoc->setModified();
}

void Fit1DFrame::updateFrame()
{
    if (m_data_source->simuData1DItem() && m_data_source->realData1DItem()) {
        m_data_canvas->setData1DItems(
            {m_data_source->simuData1DItem(), m_data_source->realData1DItem()});
        m_diff_canvas->setData1DItems({m_data_source->diffData1DItem()});
        // sync X axis view area between simulated and difference plots
        // simu --> diff
        connect(m_data_source->simuData1DItem(), &DataItem::updateOtherPlots,
                m_data_source->diffData1DItem(), &DataItem::checkXranges, Qt::UniqueConnection);
        // diff --> simu
        connect(m_data_source->diffData1DItem(), &DataItem::updateOtherPlots,
                m_data_source->simuData1DItem(), &DataItem::checkXranges, Qt::UniqueConnection);

        // update diff data if simulation data changes
        connect(m_data_source->simuData1DItem(), &Data1DItem::datafieldChanged, this,
                &Fit1DFrame::updateDiffData, Qt::UniqueConnection);
        updateDiffData();
        show();
    } else
        hide();
}

void Fit1DFrame::updateDiffData()
{
    ASSERT(m_data_source->simuData1DItem() && m_data_source->diffData1DItem()
           && m_data_source->realData1DItem());
    if (!m_data_source->simuData1DItem()->c_field() || !m_data_source->realData1DItem()->c_field())
        return;

    m_data_source->diffData1DItem()->setDatafield(DiffUtil::relativeDifferenceField(
        *m_data_source->simuData1DItem()->c_field(), *m_data_source->realData1DItem()->c_field()));

    // keep Y axis range up with data range
    double min = m_data_source->diffData1DItem()->yMin();
    double max = m_data_source->diffData1DItem()->yMax();
    if (!m_data_source->diffData1DItem()->axItemY()->isLogScale() || min > 0)
        m_data_source->diffData1DItem()->setYrange(min, max);
}
