//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Frame/Fit2DFrame.cpp
//! @brief     Implements class Fit2DFrame.
//!
//! @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/Fit2DFrame.h"
#include "Base/Util/Assert.h"
#include "Device/Data/DataUtil.h"
#include "Device/Data/Datafield.h"
#include "GUI/Model/Data/Data1DItem.h"
#include "GUI/Model/Data/Data2DItem.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/ColorMapCanvas.h"
#include "GUI/View/Canvas/ProgressCanvas.h"
#include "GUI/View/Plotter/ColorMap.h"
#include "GUI/View/Plotter/PlotStatusLabel.h"
#include "GUI/View/Plotter/SavePlot.h"
#include "GUI/View/Setup/AxesPanel.h"
#include "GUI/View/Setup/Data2DToolbar.h"
#include "GUI/View/Setup/FrameActions.h"

Fit2DFrame::Fit2DFrame()
    : AnydataFrame(std::make_unique<DataFromJob>())
    , m_real_canvas(new ColorMapCanvas(false))
    , m_simu_canvas(new ColorMapCanvas(false))
    , m_diff_canvas(new ColorMapCanvas(false))
    , m_status_label(new PlotStatusLabel(
          {m_real_canvas->colorMap(), m_simu_canvas->colorMap(), m_diff_canvas->colorMap()}))
{
    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_real_canvas, 0, 0);
    gridLayout->addWidget(m_simu_canvas, 0, 1);
    gridLayout->addWidget(m_diff_canvas, 1, 0);
    auto* progress_canvas = new ProgressCanvas;
    gridLayout->addWidget(progress_canvas, 1, 1);

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

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

    auto* axes_panel = new AxesPanel(dataSource());
    hlayout->addWidget(axes_panel);
    axes_panel->hide();

    auto* toolbar = new Data2DToolbar(false, false);
    hlayout->addWidget(toolbar);

    updateFrame();

    //... Connect signals from toolbar

    connect(toolbar->actions()->toggle_properties_panel, &QAction::triggered, axes_panel,
            &QWidget::setVisible);

    connect(toolbar->actions()->reset_view, &QAction::triggered, this,
            &Fit2DFrame::onResetViewAction);

    connect(toolbar->actions()->save_plot, &QAction::triggered, [this] {
        ASSERT(simuItem());
        GUI::Plot::savePlot(m_simu_canvas->colorMap(), simuItem()->c_field());
    });


    //... Connect model

    connect(gDoc->jobs(), &JobsSet::jobPlotContextChanged, this, &Fit2DFrame::updateFrame);
}

Fit2DFrame::~Fit2DFrame() = default;

void Fit2DFrame::updateFrame()
{
    if (!hasProperContext()) {
        hide();
        return;
    }

    GUI::Util::Ranges::setCommonRangeZ(dataSource()->mainData2DItems());
    updateDiffData();
    connectItems();

    m_simu_canvas->itemToCanvas(simuItem());
    m_real_canvas->itemToCanvas(realItem());
    m_diff_canvas->itemToCanvas(diffItem());

    show();
}

void Fit2DFrame::updateDiffData()
{
    if (!hasProperContext())
        return;

    if (!simuItem()->c_field() || !realItem()->c_field())
        return;

    diffItem()->setDatafield(
        DataUtil::relativeDifferenceField(*simuItem()->c_field(), *realItem()->c_field()));

    // keep Z axis range up with data range
    diffItem()->computeDataRange();
}

void Fit2DFrame::updateCommonDataRange()
{
    if (!hasProperContext())
        return;

    simuItem()->updateDataRange();
    // adjust both frame and data range of real data
    realItem()->setDatafield({simuItem()->c_field()->frame(), realItem()->c_field()->flatVector()});
    GUI::Util::Ranges::setCommonRangeZ({simuItem(), realItem()});
}

void Fit2DFrame::connectItems()
{
    ASSERT(hasProperContext());

    // sync XY view area between simulated, real and diff plots
    for (auto* senderItem : dataSource()->allData2DItems())
        for (auto* receiverItem : dataSource()->allData2DItems())
            if (receiverItem != senderItem)
                connect(senderItem, &DataItem::updateOtherPlots, receiverItem,
                        &DataItem::alignXYranges, Qt::UniqueConnection);

    // sync Z range: simu --> real
    connect(simuItem(), &DataItem::updateOtherPlots, realItem(), &Data2DItem::copyZRangeFromItem,
            Qt::UniqueConnection);

    // sync Z range: real --> simu
    connect(realItem(), &DataItem::updateOtherPlots, simuItem(), &Data2DItem::copyZRangeFromItem,
            Qt::UniqueConnection);

    // sync ranges between simulated and real when recomputed
    connect(simuItem(), &Data2DItem::datafieldChanged, this, &Fit2DFrame::updateCommonDataRange,
            Qt::UniqueConnection);

    // update diff data if simulation data changes
    connect(simuItem(), &DataItem::datafieldChanged, this, &Fit2DFrame::updateDiffData,
            Qt::UniqueConnection);
}

void Fit2DFrame::onResetViewAction()
{
    ASSERT(hasProperContext());

    simuItem()->resetView();
    realItem()->resetView();
    diffItem()->resetView();

    // synchronize data range between simulated and real
    GUI::Util::Ranges::setCommonRangeZ(dataSource()->mainData2DItems());
    gDoc->setModified();
}

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

bool Fit2DFrame::hasProperContext() const
{
    return bool(simuItem() && realItem() && diffItem());
}

Data2DItem* Fit2DFrame::realItem() const
{
    return dataSource()->realData2DItem();
}

Data2DItem* Fit2DFrame::simuItem() const
{
    return dataSource()->simuData2DItem();
}

Data2DItem* Fit2DFrame::diffItem() const
{
    return dataSource()->diffData2DItem();
}
