//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      GUI/View/Canvas/MaskEditorCanvas.cpp
//! @brief     Implements class MaskEditorCanvas.
//!
//! @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/Canvas/MaskEditorCanvas.h"
#include "Device/Data/Datafield.h"
#include "Device/Mask/IShape2D.h"
#include "Device/Mask/MaskStack.h"
#include "GUI/Model/Data/Data2DItem.h"
#include "GUI/Model/Mask/MasksSet.h"
#include "GUI/Model/Project/ProjectDocument.h"
#include "GUI/View/Canvas/SavePlotAssistant.h"
#include "GUI/View/Plotter/ColorMap.h"
#include "GUI/View/Plotter/PlotEventHelper.h"
#include "GUI/View/Plotter/PlotStatusLabel.h"
#include "GUI/View/Scene/MaskGraphicsScene.h"
#include "GUI/View/Scene/MaskGraphicsView.h"
#include "GUI/View/Setup/FrameActions.h"
#include <QVBoxLayout>

namespace {

Datafield* createMaskedField(const Data2DItem* data)
{
    // Requesting mask information
    std::unique_ptr<IShape2D> roi;
    Datafield* result = data->c_field()->clone();
    MaskStack detectorMask;
    for (const MaskItem* t : *data->masks()) {
        if (t->isVisible()) {
            if (auto* roiItem = dynamic_cast<const RegionOfInterestItem*>(t))
                roi = roiItem->createShape();
            else {
                std::unique_ptr<IShape2D> shape(t->createShape());
                detectorMask.pushMask(*shape, t->maskValue(), false);
            }
        }
    }

    // ROI mask has to be the last one, it can not be "unmasked" by other shapes
    if (roi)
        detectorMask.pushMask(*roi, true, false);

    if (!detectorMask.hasMasks())
        return nullptr;

    for (size_t i = 0; i < result->size(); ++i)
        if (detectorMask.isMasked(i, result->frame()))
            (*result)[i] = 0;

    return result;
}

} // namespace


MaskEditorCanvas::MaskEditorCanvas()
    : m_scene(new MaskGraphicsScene)
    , m_view(new MaskGraphicsView(m_scene))
    , m_status_label(new PlotStatusLabel(nullptr))
    , m_data_item(nullptr)
    , m_current_activity(Canvas2DMode::SELECTION)
{
    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    setMinimumSize(400, 400);

    auto* layout = new QVBoxLayout;
    setLayout(layout);
    layout->setContentsMargins(0, 0, 0, 0);
    layout->setSpacing(0);
    layout->addWidget(m_view);
    layout->addWidget(m_status_label);

    connect(gActions->reset_view, &QAction::triggered, this, &MaskEditorCanvas::onResetViewRequest);
    connect(m_view, &MaskGraphicsView::changeActivityRequest, this,
            &MaskEditorCanvas::changeActivityRequest);
    connect(m_view, &MaskGraphicsView::deleteSelectedRequest, this,
            &MaskEditorCanvas::deleteSelectedRequest);
    // automatically switch to the appropriate projection tab
    connect(m_scene, &MaskGraphicsScene::lineItemMoved, [this](LineItem* sender) {
        if (dynamic_cast<HorizontalLineItem*>(sender)
            && (m_current_activity != Canvas2DMode::VERTICAL_PRJN))
            emit changeProjectionsTabRequest(Canvas2DMode::HORIZONTAL_PRJN);
        if (dynamic_cast<VerticalLineItem*>(sender)
            && (m_current_activity != Canvas2DMode::HORIZONTAL_PRJN))
            emit changeProjectionsTabRequest(Canvas2DMode::VERTICAL_PRJN);
    });
}

MaskEditorCanvas::~MaskEditorCanvas() = default;

void MaskEditorCanvas::updateCanvas(Data2DItem* data_item)
{
    ASSERT(data_item);
    m_data_item = data_item;

    m_scene->associateItems(data_item);

    m_scene->updateSize(m_view->size());

    m_status_label->setPlot(m_scene->colorMap());

    // notify ProjectionPlot about the changes
    connect(m_scene, &MaskGraphicsScene::lineItemProcessed, data_item,
            &Data2DItem::projectionCreated, Qt::UniqueConnection);
    connect(m_scene, &MaskGraphicsScene::lineItemMoved, data_item,
            &Data2DItem::projectionPositionChanged, Qt::UniqueConnection);
    connect(m_scene, &MaskGraphicsScene::lineItemDeleted, data_item, &Data2DItem::projectionGone,
            Qt::UniqueConnection);
    auto* cm = m_scene->colorMap();
    ASSERT(cm);
    connect(cm->eventHelper(), &PlotEventHelper::enteringPlot, this,
            &MaskEditorCanvas::onEnteringColorMap, Qt::UniqueConnection);
    connect(cm->eventHelper(), &PlotEventHelper::leavingPlot, this,
            &MaskEditorCanvas::onLeavingColorMap, Qt::UniqueConnection);
    connect(cm->eventHelper(), &PlotEventHelper::positionChanged, this,
            &MaskEditorCanvas::onPositionChanged, Qt::UniqueConnection);
    connect(cm, &ColorMap::marginsChanged, this, &MaskEditorCanvas::marginsChanged,
            Qt::UniqueConnection);

    onLeavingColorMap();
    m_scene->onActivityChanged(m_current_activity); // TODO call no longer needed ?
}

void MaskEditorCanvas::onPresentationChange(bool pixelized)
{
    m_scene->clearSelection(); // important to avoid crash (unsubscribe while calling subscribers)

    ASSERT(m_data_item);

    if (pixelized) {
        if (Datafield* maskedData = ::createMaskedField(m_data_item)) {
            // store data backup
            m_backup_data.reset(m_data_item->c_field()->clone());
            m_backup_interpolated = m_data_item->isInterpolated();

            m_data_item->setDatafield(*maskedData);
            m_data_item->setInterpolated(false);
        } else {
            m_backup_data.reset();
        }

    } else if (m_backup_data) {
        m_data_item->setDatafield(*m_backup_data);
        m_data_item->setInterpolated(m_backup_interpolated);
    }

    if (auto* container = m_data_item->masks())
        for (MaskItem* t : *container)
            t->setIsVisible(!pixelized && t->wasVisible());
}

//! Saves plot into project directory.

void MaskEditorCanvas::onSavePlotRequest()
{
    GUI::Plot::savePlot(m_scene->colorMap()->customPlot(), m_data_item->c_field());
}

void MaskEditorCanvas::onPositionChanged(double x, double y)
{
    if (m_live_projection) {
        if (m_current_activity == Canvas2DMode::HORIZONTAL_PRJN)
            m_live_projection->setPos(y);
        else if (m_current_activity == Canvas2DMode::VERTICAL_PRJN)
            m_live_projection->setPos(x);
        emit m_data_item->projectionPositionChanged(m_live_projection.get());
    }
}

void MaskEditorCanvas::onResetViewRequest()
{
    m_view->onResetViewRequest();

    if (m_data_item->isZoomed())
        m_data_item->resetView();
    else
        setZoomToROI();
    gDoc->setModified();
}

void MaskEditorCanvas::setZoomToROI()
{
    if (const MasksSet* masks = m_data_item->masks()) {
        if (const auto* roiItem = masks->regionOfInterestItem()) {
            m_data_item->setXrange(roiItem->xLow(), roiItem->xUp());
            m_data_item->setYrange(roiItem->yLow(), roiItem->yUp());
        }
    }
}

void MaskEditorCanvas::onEnteringColorMap()
{
    if (m_live_projection)
        return;

    if (m_current_activity == Canvas2DMode::HORIZONTAL_PRJN)
        m_live_projection = std::make_unique<HorizontalLineItem>();
    else if (m_current_activity == Canvas2DMode::VERTICAL_PRJN)
        m_live_projection = std::make_unique<VerticalLineItem>();

    if (m_live_projection) {
        m_live_projection->setIsVisible(false);
        emit m_data_item->projectionPositionChanged(m_live_projection.get());
    }
}

void MaskEditorCanvas::onLeavingColorMap()
{
    if (m_live_projection) {
        disconnect(m_live_projection.get(), nullptr, m_data_item, nullptr);
        emit m_data_item->projectionGone(m_live_projection.get());
        m_live_projection.reset();
    }
}

void MaskEditorCanvas::setCanvasMode(Canvas2DMode::Flag mode)
{
    m_current_activity = mode;
    m_scene->onActivityChanged(mode);
    onLeavingColorMap();
}
