//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/PlotComparison/FitComparisonWidget.cpp
//! @brief     Implements class FitComparisonWidget
//!
//! @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/FitComparisonWidget.h"
#include "Base/Util/Assert.h"
#include "Device/Histo/DiffUtil.h"
#include "GUI/Model/Axis/BasicAxisItem.h"
#include "GUI/Model/Data/IntensityDataItem.h"
#include "GUI/Model/Data/SpecularDataItem.h"
#include "GUI/Model/Job/FitSuiteItem.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/Support/Util/ActionFactory.h"
#include "GUI/View/Common/IntensityDataPropertyWidget.h"
#include "GUI/View/Plot2D/ColorMapCanvas.h"
#include "GUI/View/PlotComparison/FitFlowWidget.h"
#include "GUI/View/PlotUtil/ColorMap.h"
#include "GUI/View/PlotUtil/PlotStatusLabel.h"
#include "GUI/View/PlotUtil/RangeUtil.h"
#include <QAction>
#include <QVBoxLayout>

FitComparisonWidget::FitComparisonWidget(QWidget* parent)
    : DataAccessWidget(parent)
    , m_realCanvas(new ColorMapCanvas(this))
    , m_simuCanvas(new ColorMapCanvas(this))
    , m_diffCanvas(new ColorMapCanvas(this))
    , m_fitFlowWidget(new FitFlowWidget(this))
    , m_statusLabel(new PlotStatusLabel(nullptr, this))
    , m_propertyWidget(new IntensityDataPropertyWidget(this))
    , m_resetViewAction(new QAction(this))
{
    auto* vlayout = new QVBoxLayout;
    vlayout->setContentsMargins(0, 0, 0, 0);
    vlayout->setSpacing(0);

    m_propertyWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);

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

    gridLayout->addWidget(m_realCanvas, 0, 0);
    gridLayout->addWidget(m_simuCanvas, 0, 1);
    gridLayout->addWidget(m_diffCanvas, 1, 0);
    gridLayout->addWidget(m_fitFlowWidget, 1, 1);

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

    m_statusLabel->reset();
    m_statusLabel->addPlot(m_realCanvas->colorMap());
    m_statusLabel->addPlot(m_simuCanvas->colorMap());
    m_statusLabel->addPlot(m_diffCanvas->colorMap());

    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, &FitComparisonWidget::onResetViewAction,
            Qt::UniqueConnection);

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

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

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

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

    if (oldJob != jobItem())
        GUI::View::RangeUtil::setCommonRangeZ(mainIntensityDataItems());
    updateDiffData();
    connectItems();

    m_simuCanvas->setIntensityItem(simuIntensityDataItem());
    m_realCanvas->setIntensityItem(realIntensityDataItem());
    m_diffCanvas->setIntensityItem(diffIntensityDataItem());
    m_fitFlowWidget->setJobOrRealItem(job_item);
    m_propertyWidget->setJobOrRealItem(job_item);
}

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

void FitComparisonWidget::onResetViewAction()
{
    ASSERT(simuIntensityDataItem() && diffIntensityDataItem() && realIntensityDataItem());
    simuIntensityDataItem()->resetView();
    realIntensityDataItem()->resetView();
    diffIntensityDataItem()->resetView();

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

void FitComparisonWidget::connectItems()
{
    // sync XY view area between sumulated, real and difference plots
    for (auto senderItem : allIntensityDataItems())
        for (auto receiverItem : allIntensityDataItems())
            if (receiverItem != senderItem)
                connect(senderItem, &DataItem::updateOtherPlots, receiverItem,
                        &DataItem::copyXYRangesFromItem, Qt::UniqueConnection);

    // sync Z range between sumulated and real
    connect(simuIntensityDataItem(), &IntensityDataItem::alignRanges,
            [this] { GUI::View::RangeUtil::setCommonRangeZ(mainIntensityDataItems()); });

    // sync Z range: simu --> real
    connect(simuIntensityDataItem(), &DataItem::updateOtherPlots, realIntensityDataItem(),
            &IntensityDataItem::copyZRangeFromItem, Qt::UniqueConnection);

    // sync Z range: real --> simu
    connect(realIntensityDataItem(), &DataItem::updateOtherPlots, simuIntensityDataItem(),
            &IntensityDataItem::copyZRangeFromItem, Qt::UniqueConnection);

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

void FitComparisonWidget::updateDiffData()
{
    ASSERT(simuIntensityDataItem() && diffIntensityDataItem() && realIntensityDataItem());
    if (!simuIntensityDataItem()->c_field() || !realIntensityDataItem()->c_field())
        return;

    diffIntensityDataItem()->setDatafield(DiffUtil::relativeDifferenceField(
        *simuIntensityDataItem()->c_field(), *realIntensityDataItem()->c_field()));

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