/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQPlotView.h"

#include <QtGlobal>
#include <QtAlgorithms>
#include <QApplication>
#include <QGuiApplication>
#include <QClipboard>
#include <QContextMenuEvent>
#include <QDebug>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPlainTextEdit>
#include <QResizeEvent>
#include <QScrollBar>
#include <QTimer>
#include <QUndoStack>

#include <QGraphicsSimpleTextItem>
#include <QGraphicsDropShadowEffect>

#include "MvKeyProfile.h"

#include "MgQLayoutItem.h"
#include "MgQPlotScene.h"
#include "MgQRootItem.h"
#include "MgQSceneItem.h"

#include "MvQAreaSelection.h"
#include "MvQLineSelection.h"
#include "MvQCursorData.h"
#include "MvQMagnifier.h"
#include "MvQMethods.h"
#include "MvQPlaceMark.h"
#include "MvQPointSelection.h"
#include "MvQZoom.h"
#include "uPlotBase.h"
#include "MvQFeatureCommand.h"
#include "MvQFeatureCommandTarget.h"
#include "MvQFeatureRibbonEditor.h"
#include "MvQFeatureFactory.h"
#include "MvQFeatureItem.h"
#include "MvQFeatureSelector.h"
#include "MvQFeatureContextMenu.h"

#define METVIEW_MULTIPAGE

//#define PLOTVIEWFEATURE_DEBUG_

MvQPlotView::MvQPlotView(QGraphicsScene* scene, QWidget* parent) :
    QGraphicsView(scene, parent)
{
    plotScene_ = static_cast<MgQPlotScene*>(scene);

    resetTransform();
    //translate(0,height);
    scale(1., -1);

    setAcceptDrops(true);

    MvQFeatureHandler::Instance()->setView(this);
}

void MvQPlotView::slotSetEnableZoom(bool flag)
{
    if (zoom_ && flag == zoom_->activated())
        return;

    if (flag == false) {
        if (!zoom_)
            return;
        else {
            zoom_->setActivated(false);
            viewport()->unsetCursor();
        }
    }
    else {
        if (!zoom_) {
            zoom_ = new MvQZoom(plotScene_, this, nullptr);
            //zoom_->setVisible(true);
            zoom_->setZValue(2.5);
            plotScene_->addItem(zoom_);

            connect(zoom_, SIGNAL(zoomRectangleIsDefined(const std::string&, const std::string&)),
                    this, SIGNAL(zoomRectangleIsDefined(const std::string&, const std::string&)));

            connect(zoom_, SIGNAL(mousePressInSceneItem(MgQSceneItem*)),
                    this, SIGNAL(zoomActionStarted(MgQSceneItem*)));
        }

        zoom_->setActivated(true);
        emit magnifierIsEnabledProgramatically(false);
        emit inputSIsEnabledProgramatically(false);
        viewport()->setCursor(QCursor(Qt::CrossCursor));

        // feature selector needs to be cleared
        MvQFeatureHandler::Instance()->clearSelection();
        //        if (selector_) {
        //            selector_->clear();
        //        }
    }
}

void MvQPlotView::slotSetEnableMagnifier(bool flag)
{
    if (magnifier_ && flag == magnifier_->activated())
        return;

    if (flag == false) {
        if (!magnifier_)
            return;
        else {
            magnifier_->setActivated(false);
            //magnifier_->operationEnd();
        }
    }
    else {
        emit zoomIsEnabledProgramatically(false);
        if (!magnifier_) {
            magnifier_ = new MvQMagnifier(plotScene_, this, nullptr);
            //zoom_->setVisible(true);
            magnifier_->setZValue(1.5);
            plotScene_->addItem(magnifier_);

            connect(magnifier_, SIGNAL(factorChanged()),
                    this, SLOT(slotMagnifierChanged()));

            connect(magnifier_, SIGNAL(positionChanged()),
                    this, SLOT(slotMagnifierChanged()));
        }

        magnifier_->setActivated(true);
        //magnifier_->operationBegin();
    }

    slotMagnifierChanged();
}

void MvQPlotView::slotMagnifierChanged()
{
    if (!magnifier_)
        return;

    //Update dataprobe
    if (dataProbe_ && dataProbe_->activated()) {
        dataProbe_->setMagnifier(magnifier_);
    }
}

void MvQPlotView::slotSetEnableCursorData(bool flag)
{
    if (cursorData_ && flag == cursorData_->activated())
        return;

    if (flag == false) {
        if (!cursorData_)
            return;
        else {
            cursorData_->setActivated(false);
        }
    }
    else {
        if (!cursorData_) {
            cursorData_ = new MvQCursorData(plotScene_, this, nullptr);
            cursorData_->setVisible(true);
            cursorData_->setZValue(2.);
            plotScene_->addItem(cursorData_);

            connect(cursorData_, SIGNAL(positionChanged(QPointF, QList<ValuesCollector>)),
                    this, SIGNAL(cursorDataChanged(QPointF, QList<ValuesCollector>)));
        }

        cursorData_->setActivated(true);
    }
}

void MvQPlotView::slotSetEnableAreaSelection(bool flag)
{
    if (area_ && flag == area_->activated())
        return;

    if (flag == false) {
        if (!area_)
            return;
        else {
            area_->setActivated(false);
        }
    }
    else {
        if (!area_) {
            area_ = new MvQAreaSelection(plotScene_, this, nullptr);
            area_->setParentItem(plotScene_->annotationRootItem());
            area_->setZValue(2.);

            connect(area_, SIGNAL(areaIsDefined(double, double, double, double)),
                    this, SIGNAL(areaIsDefined(double, double, double, double)));

            connect(area_, SIGNAL(areaIsUndefined()),
                    this, SIGNAL(areaIsUndefined()));
        }

        area_->setActivated(true);
        emit magnifierIsEnabledProgramatically(false);
        emit zoomIsEnabledProgramatically(false);
    }
}
void MvQPlotView::slotChangeArea(double blLat, double blLon, double trLat, double trLon)
{
    if (!area_ || !area_->activated())
        return;

    area_->setArea(blLat, blLon, trLat, trLon);
}

void MvQPlotView::slotClearArea()
{
    if (!area_ || !area_->activated())
        return;

    area_->clearArea();
}

void MvQPlotView::slotSelectAllArea()
{
    if (!area_ || !area_->activated())
        return;

    area_->selectAllArea();
}

void MvQPlotView::slotSetEnableLineSelection(bool flag)
{
    if (line_ && flag == line_->activated())
        return;

    if (flag == false) {
        if (!line_)
            return;
        else {
            line_->setActivated(false);
        }
    }
    else {
        if (!line_) {
            line_ = new MvQLineSelection(plotScene_, this, nullptr);
            line_->setParentItem(plotScene_->annotationRootItem());
            line_->setZValue(2.);

            connect(line_, SIGNAL(lineIsDefined(double, double, double, double)),
                    this, SIGNAL(lineIsDefined(double, double, double, double)));

            connect(line_, SIGNAL(lineIsUndefined()),
                    this, SIGNAL(lineIsUndefined()));
        }

        line_->setActivated(true);
        emit magnifierIsEnabledProgramatically(false);
        emit zoomIsEnabledProgramatically(false);
    }
}
void MvQPlotView::slotChangeLine(double lat1, double lon1, double lat2, double lon2)
{
    if (!line_ || !line_->activated())
        return;

    line_->setLine(lat1, lon1, lat2, lon2);
}

void MvQPlotView::slotClearLine()
{
    if (!line_ || !line_->activated())
        return;

    line_->clearLine();
}
void MvQPlotView::slotChangeLineDirection()
{
    if (!line_ || !line_->activated())
        return;

    line_->changeDirection();
}

void MvQPlotView::slotSetEnablePointSelection(bool flag)
{
    if (point_ && flag == point_->activated())
        return;

    if (flag == false) {
        if (!point_)
            return;
        else {
            point_->setActivated(false);
        }
    }
    else {
        if (!point_) {
            point_ = new MvQPointSelection(plotScene_, this, nullptr);
            point_->setParentItem(plotScene_->annotationRootItem());
            point_->setZValue(2.);

            connect(point_, SIGNAL(pointIsDefined(double, double)),
                    this, SIGNAL(pointIsDefined(double, double)));

            connect(point_, SIGNAL(pointIsUndefined()),
                    this, SIGNAL(pointIsUndefined()));
        }

        point_->setActivated(true);
        emit magnifierIsEnabledProgramatically(false);
        emit zoomIsEnabledProgramatically(false);
    }
}
void MvQPlotView::slotChangePoint(double lat, double lon)
{
    if (!point_ || !point_->activated())
        return;

    point_->setPoint(lat, lon);
}

void MvQPlotView::slotClearPoint()
{
    if (!point_ || !point_->activated())
        return;

    point_->clearPoint();
}


void MvQPlotView::slotSelectScene()
{
    sceneIsBeingSelected_ = true;
    viewport()->setCursor(Qt::PointingHandCursor);
}

void MvQPlotView::setDataProbe(MvQPlaceMark* p)
{
    dataProbe_ = p;
}

void MvQPlotView::enterFeatureAddMode()
{
    emit zoomIsEnabledProgramatically(false);
    inFeatureAddMode_ = true;
    viewport()->setCursor(QCursor(Qt::CrossCursor));
    MvQFeatureHandler::Instance()->clearSelection();
}

void MvQPlotView::leaveFeatureAddMode()
{
    if (inFeatureAddMode_) {
        inFeatureAddMode_ = false;
        viewport()->setCursor(QCursor());
    }
}

void MvQPlotView::initFeatureInterface()
{
    //  It is guaranteed (internally) to be done only once.
    MvQFeatureMenuItem::setupShortcut(this, this, "view");

    // This is a one-off action
    if (!featureRibbonEditor_) {
        featureRibbonEditor_ = new MvQFeatureRibbonEditor(this);
        emit addRibbonEditor(featureRibbonEditor_);
        MvQFeatureHandler::Instance()->setRibbonEditor(featureRibbonEditor_);
    }
}

bool MvQPlotView::hasFeatures() const
{
    return MvQFeatureHandler::Instance()->features().size() > 0;
}

void MvQPlotView::resetBegin()
{
    if (zoom_)
        zoom_->prepareForReset();
    if (magnifier_)
        magnifier_->prepareForReset();
    if (cursorData_)
        cursorData_->prepareForReset();
    if (area_)
        area_->prepareForReset();
    if (line_)
        line_->prepareForReset();
    if (point_)
        point_->prepareForReset();
    if (dataProbe_)
        dataProbe_->prepareForReset();

    MvQFeatureHandler::Instance()->resetBegin();
}

void MvQPlotView::resetEnd()
{
    if (zoom_)
        zoom_->reset();
    if (magnifier_)
        magnifier_->reset();
    if (cursorData_)
        cursorData_->reset();
    if (area_)
        area_->reset();
    if (line_)
        line_->reset();
    if (point_)
        point_->reset();
    if (dataProbe_)
        dataProbe_->reset();

    MvQFeatureHandler::Instance()->resetEnd();
}

void MvQPlotView::mouseDoubleClickEvent(QMouseEvent* event)
{
    if (zoom_ && zoom_->activated()) {
        return;
    }
    else {
        QGraphicsView::mouseDoubleClickEvent(event);
    }
}

void MvQPlotView::mousePressEvent(QMouseEvent* event)
{
    bool eventProcessed = false;
    QPointF scenePos    = mapToScene(event->pos());

    if (sceneIsBeingSelected_) {
        viewport()->unsetCursor();
        sceneIsBeingSelected_ = false;
        QPointF sPos          = mapToScene(event->pos());
        emit sceneSelected(sPos);
        return;
    }

    // add new feature
    if (inFeatureAddMode_) {
        leaveFeatureAddMode();
        MvQFeatureHandler::Instance()->addFromView(event->pos());
        return;
    }

    // close feature text editor if we click outside the editor
    if (MvQFeatureHandler::Instance()->checkMousePressForTextEditor(scenePos)) {
        return;
    }

    MvQMagnifier::MagnifierAction magnifierAction = MvQMagnifier::NoAction;

    //Zoom and magnifier are exclusive!

    //First comes the zoom if it is active
    if (zoom_ && zoom_->activated()) {
        zoom_->mousePressEventFromView(event);
        return;
    }

    //Then we check if the dataProbe is selected
    if (dataProbe_ && dataProbe_->activated()) {
        //		QPointF pos = mapToScene(event->pos());
        dataProbe_->mousePressEventFromView(event);
        if (dataProbe_->currentAction() != MvQPlaceMark::NoAction) {
            return;
        }
    }

    //If there is no zoom and the dataProbe is not selected tries the magnifier
    if (magnifier_ && magnifier_->activated()) {
        magnifier_->mousePressEventFromView(event);
        magnifierAction = magnifier_->magnifierAction();
    }

    //We continuew with the ...

    //area selection
    if (area_ && area_->activated()) {
        area_->mousePressEventFromView(event);
    }
    //line selection
    else if (line_ && line_->activated()) {
        line_->mousePressEventFromView(event);
    }
    //point selection
    else if (point_ && point_->activated()) {
        point_->mousePressEventFromView(event);
    }
    // symbols/features
    else if (MvQFeatureHandler::Instance()->features().size() > 0) {
        // indicate that a feature control point edit can be started
        //        if (selector_) {
        //            selector_->startPointEdit();
        //        }
        QGraphicsView::mousePressEvent(event);
        eventProcessed = true;
#if 0
        if (selector_) {
#ifdef PLOTVIEWFEATURE_DEBUG_
            qDebug() << "CHECK CLICK IN SELECTOR";
#endif
#if 0
            selector_->checkPointEdit();
            selector_->startPointEdit();

            if (selector_->items().size() > 0 || selector_->hasPointEditIten() || editor_) {
                return;
            }
#endif
        }
#endif
    }

    //Cursor data can be active both for zoom and magnifier.
    //If the mousepress event triggered some actions in the magnifier
    //then we ignore this event for the cursor data.
    if (cursorData_ && cursorData_->activated() &&
        magnifierAction == MvQMagnifier::NoAction &&
        (!zoom_ || zoom_->activated() == false)) {
        cursorData_->mousePressEventFromView(event);
    }
    else if (!eventProcessed) {
        QGraphicsView::mousePressEvent(event);
    }
}

void MvQPlotView::mouseMoveEvent(QMouseEvent* event)
{
    if (cursorData_ && cursorData_->activated()) {
        QPointF pos = mapToScene(event->pos());

        //If the cursor is inside tha magnifier cursor data has to
        //be notified know about the magnifier geometry.
        if (magnifier_ && magnifier_->activated() &&
            magnifier_->checkPointInMagnifier(pos)) {
            cursorData_->setMagnifierInfo(true, magnifier_->pos(), magnifier_->zoomFactor());
        }
        else {
            cursorData_->setMagnifierInfo(false);
        }

        cursorData_->mouseMoveEventFromView(event);
    }

    if (dataProbe_ && dataProbe_->activated() && dataProbe_->currentAction() == MvQPlaceMark::MoveAction) {
        //		QPointF pos = mapToScene(event->pos());
        dataProbe_->mouseMoveEventFromView(event);
        return;
    }

    if (zoom_ && zoom_->activated()) {
        zoom_->mouseMoveEventFromView(event);
    }
    else if (area_ && area_->activated()) {
        area_->mouseMoveEventFromView(event);
    }
    else if (line_ && line_->activated()) {
        line_->mouseMoveEventFromView(event);
    }
    else if (point_ && point_->activated()) {
        point_->mouseMoveEventFromView(event);
    }
    else if (magnifier_ && magnifier_->activated()) {
        magnifier_->mouseMoveEventFromView(event);
    }
    else {
        MvQFeatureHandler::Instance()->checkMouseMove(event);
        QGraphicsView::mouseMoveEvent(event);
    }

    return;
}

void MvQPlotView::mouseReleaseEvent(QMouseEvent* event)
{
    bool zoomStatus = true;

    if (zoom_ && zoom_->activated()) {
        zoom_->mouseReleaseEventFromView(event);
        zoomStatus = zoom_->zoomWasPerformedAfterMouseRelease();
    }
    else if (area_ && area_->activated()) {
        area_->mouseReleaseEventFromView(event);
    }
    else if (line_ && line_->activated()) {
        line_->mouseReleaseEventFromView(event);
    }
    else if (point_ && point_->activated()) {
        point_->mouseReleaseEventFromView(event);
    }
    else if (dataProbe_ && dataProbe_->activated()) {
        dataProbe_->mouseReleaseEventFromView(event);
    }
    else {
        QGraphicsView::mouseReleaseEvent(event);
    }

    if (cursorData_ && cursorData_->activated()) {
        cursorData_->setZoomWasPerformedAfterMouseRelease(zoomStatus);
        cursorData_->mouseReleaseEventFromView(event);
    }
    if (magnifier_ && magnifier_->activated()) {
        magnifier_->mouseReleaseEventFromView(event);
    }
    else {
        QGraphicsView::mouseReleaseEvent(event);
    }

    return;
}

void MvQPlotView::itemSelected(MvQFeatureItem*)
{
    MvQFeatureHandler::Instance()->checkSelection();
}

//===========================
// Drop from the new Desktop
//===========================

void MvQPlotView::dragEnterEvent(QDragEnterEvent* event)
{
    if ((event->proposedAction() == Qt::CopyAction ||
         event->proposedAction() == Qt::MoveAction)) {
        event->accept();
    }
}

void MvQPlotView::dragMoveEvent(QDragMoveEvent* event)
{
    if ((event->proposedAction() == Qt::CopyAction ||
         event->proposedAction() == Qt::MoveAction)) {
        event->accept();
    }
}

void MvQPlotView::dropEvent(QDropEvent* event)
{
    MvQDrop drop(event);

    if (drop.hasData()) {
        QPoint p(horizontalScrollBar()->value(), verticalScrollBar()->value());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        p += event->position().toPoint();
#else
        p += event->pos();
#endif
        emit iconDropped(drop, p);
        event->accept();
        return;
    }

    event->ignore();
}

// callback from shortcuts
void MvQPlotView::slotCommandShortcut()
{
    if (auto* sc = static_cast<QShortcut*>(QObject::sender())) {
        auto cmd = sc->property("id").toString();
#ifdef PLOTVIEWFEATURE_DEBUG_
        qDebug() << "shortcut command:" << cmd;
#endif
        MvQFeatureCommandTarget sel(MvQFeatureCommandTarget::ShortcutMode);
        sel.eval(mapToScene(mapFromGlobal(QCursor::pos())));
        //featureSelection(&sel);
        MvQFeatureHandler::Instance()->runCommand(sel, cmd);
        //runCommand(sel, cmd);
    }
}

void MvQPlotView::contextMenuEvent(QContextMenuEvent* event)
{
    // This will call handleContextMenu in the end
    emit contextMenuEventHappened(event->globalPos(), mapToScene(event->pos()));
    event->accept();
}

QAction* MvQPlotView::handleContextMenu(QList<QAction*> topLevelActions, const QPoint& globalPos, const QPointF& scenePos)
{
#ifdef PLOTVIEWFEATURE_DEBUG_
    //  qDebug() << " inSelector" << inSelector << "featureMode:" << featureMode << "featureType:" << featureType << "targetItem" << targetItem;
#endif

    MvQFeatureCommandTarget sel(MvQFeatureCommandTarget::ContextMenuMode);
    sel.eval(scenePos);
    QString cmd = MvQFeatureMenuItem::execute(sel, topLevelActions, globalPos);
    if (cmd.startsWith("extra:")) {
        int idx = cmd.mid(6).toInt();
        return (idx >= 0 && idx < topLevelActions.size()) ? topLevelActions[idx] : nullptr;
    }
    else {
        MvQFeatureHandler::Instance()->runCommand(sel, cmd);
    }
    return nullptr;
}

void MvQPlotView::setUndoStack(QUndoStack* s)
{
    MvQFeatureHandler::Instance()->setUndoStack(s);
}

void MvQPlotView::setFeatureInfoWidget(QPlainTextEdit* w)
{
    MvQFeatureHandler::Instance()->setInfoWidget(w);
}

void MvQPlotView::resizeEvent(QResizeEvent* event)
{
    QGraphicsView::resizeEvent(event);
    emit resizeEventHappened(event->size());
}


void MvQPlotView::keyPressEvent(QKeyEvent* event)
{
    //    if (selector_) {
    //        switch (event->key()) {
    //            case Qt::Key_Backspace:
    //                selector()->removeAllItems();
    //                event->accept();
    //                break;
    //            default:
    //                break;
    //        }
    //    }
    QGraphicsView::keyPressEvent(event);
}


void MvQPlotView::setMvPlotSize(double /*width*/, double /*height*/)
{
    //resetTransform();
    //translate(0,height);
    //scale(1.,-1);

    //setSceneRect(0,0,width*2,height*2);
}
