//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/PlotComparison/FitComparisonWidget1D.cpp
//! @brief     Implements class FitComparisonWidget1D
//!
//! @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/PlotComparison/FitComparisonWidget1D.h"
#include "Base/Util/Assert.h"
#include "Device/Histo/DiffUtil.h"
#include "GUI/Model/Axis/AmplitudeAxisItem.h"
#include "GUI/Model/Axis/BasicAxisItem.h"
#include "GUI/Model/Data/SpecularDataItem.h"
#include "GUI/Model/Job/FitSuiteItem.h"
#include "GUI/Model/Job/JobItem.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Support/Util/ActionFactory.h"
#include "GUI/View/PlotComparison/FitFlowWidget.h"
#include "GUI/View/PlotSpecular/SpecularDataPropertyWidget.h"
#include "GUI/View/PlotSpecular/SpecularPlot.h"
#include "GUI/View/PlotSpecular/SpecularPlotCanvas.h"
#include "GUI/View/PlotUtil/PlotStatusLabel.h"
#include "GUI/View/PlotUtil/RangeUtil.h"

FitComparisonWidget1D::FitComparisonWidget1D(QWidget* parent)
    : DataAccessWidget(parent)
    , m_data_canvas(new SpecularPlotCanvas(this))
    , m_diff_canvas(new SpecularPlotCanvas(this))
    , m_fitFlowWidget(new FitFlowWidget(this))
    , m_statusLabel(new PlotStatusLabel(nullptr, this))
    , m_propertyWidget(new SpecularDataPropertyWidget(this))
    , m_resetViewAction(new QAction(this))
{
    m_propertyWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding);

    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);
    gridLayout->addWidget(m_fitFlowWidget, 1, 2, 1, 1);

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

    m_statusLabel->reset();
    m_statusLabel->addPlot(m_data_canvas->specularPlot());
    m_statusLabel->addPlot(m_diff_canvas->specularPlot());

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

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

    m_togglePropertiesAction =
        ActionFactory::createTogglePropertiesPanelAction(this, m_propertyWidget);

    m_propertyWidget->setVisible(false);
    connect(m_propertyWidget, &DataPropertyWidget::axesRangeResetRequested, this,
            &FitComparisonWidget1D::onResetViewAction, Qt::UniqueConnection);
}

void FitComparisonWidget1D::setJobOrRealItem(QObject* job_item)
{
    JobItem* oldJob = jobItem();

    DataAccessWidget::setJobOrRealItem(job_item);
    ASSERT(jobItem());

    if (oldJob != jobItem())
        GUI::View::RangeUtil::setCommonRangeY(mainSpecularDataItems());
    updateDiffData();
    connectItems();

    m_data_canvas->setSpecularItems({simuSpecularDataItem(), realSpecularDataItem()});
    m_diff_canvas->setSpecularItems({diffSpecularDataItem()});
    m_fitFlowWidget->setJobOrRealItem(job_item);
    m_propertyWidget->setJobOrRealItem(job_item);
}

QList<QAction*> FitComparisonWidget1D::actionList()
{
    return QList<QAction*>() << m_resetViewAction << m_togglePropertiesAction;
}

void FitComparisonWidget1D::onResetViewAction()
{
    ASSERT(simuSpecularDataItem() && diffSpecularDataItem() && realSpecularDataItem());
    simuSpecularDataItem()->resetView();
    realSpecularDataItem()->resetView();
    diffSpecularDataItem()->resetView();

    // synchronize data range between simulated and real
    GUI::View::RangeUtil::setCommonRangeY(mainSpecularDataItems());
    gProjectDocument.value()->setModified();
}

void FitComparisonWidget1D::connectItems()
{
    // sync X axis view area between sumulated and difference plots
    // simu --> diff
    connect(simuSpecularDataItem(), &DataItem::updateOtherPlots, diffSpecularDataItem(),
            &DataItem::copyXRangeFromItem, Qt::UniqueConnection);
    // diff --> simu
    connect(diffSpecularDataItem(), &DataItem::updateOtherPlots, simuSpecularDataItem(),
            &DataItem::copyXRangeFromItem, Qt::UniqueConnection);

    // update diff data if simulation data changes
    connect(simuSpecularDataItem(), &SpecularDataItem::datafieldChanged, this,
            &FitComparisonWidget1D::updateDiffData, Qt::UniqueConnection);
}

void FitComparisonWidget1D::updateDiffData()
{
    ASSERT(simuSpecularDataItem() && diffSpecularDataItem() && realSpecularDataItem());
    if (!simuSpecularDataItem()->c_field() || !realSpecularDataItem()->c_field())
        return;

    diffSpecularDataItem()->setDatafield(DiffUtil::relativeDifferenceField(
        *simuSpecularDataItem()->c_field(), *realSpecularDataItem()->c_field()));

    // keep Y axis range up with data range
    double min = diffSpecularDataItem()->yMin();
    if (!diffSpecularDataItem()->yAxisItem()->isLogScale() || min > 0)
        diffSpecularDataItem()->setLowerY(min);

    double max = diffSpecularDataItem()->yMax();
    if (!diffSpecularDataItem()->yAxisItem()->isLogScale() || max > 0)
        diffSpecularDataItem()->setUpperY(max);
}
