/***************************** 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 <QDebug>
#include <QMouseEvent>
#include <QPainter>

#include "MvQAreaSelection.h"
#include "MvQPlotView.h"
#include "MgQPlotScene.h"
#include "MgQLayoutItem.h"

MvQAreaSelection::MvQAreaSelection(MgQPlotScene* scene, MvQPlotView* view, QGraphicsItem* parent) :
    MvQPlotItem(scene, view, parent)
{
    pen_ = QPen(QColor(0, 0, 255, 230), 2);
    pen_.setCosmetic(true);

    brush_ = QBrush(QColor(164, 219, 255, 180));
    currentAction_ = NoAction;
    zoomLayout_ = nullptr;
    rect_ = QRectF();

    selectionPen_ = pen_;
    selectionPen_.setCosmetic(true);
    selectionPen_.setStyle(Qt::DotLine);
    resizeId_ = -1;
    physicalHandleSize_ = 8.;
    handleSize_ = 8.;
    for (int i = 0; i < 8; i++) {
        handle_ << QRectF(0., 0., handleSize_, handleSize_);
    }

    showHandles_ = false;
    handleHover_ = -1;
}

MvQAreaSelection::~MvQAreaSelection() = default;

QRectF MvQAreaSelection::boundingRect() const
{
    checkHandleSize();

    // qDebug() << "brect" << rect_;
    return rect_.adjusted(-handleSize_ / 2., -handleSize_ / 2., handleSize_ / 2., handleSize_ / 2.);
}

void MvQAreaSelection::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    checkHandleSize();

    painter->setPen(QPen());
    painter->setBrush(brush_);
    painter->drawRect(rect_);

    // Outline
    if (currentAction_ == ResizeAction || currentAction_ == MoveAction ||
        showHandles_) {
        painter->setPen(selectionPen_);
    }
    else {
        painter->setPen(pen_);
    }

    painter->setBrush(QBrush());
    painter->drawRect(rect_);

    // Handlers
    if (showHandles_) {
        for (int i = 0; i < handle_.count(); i++) {
            QRectF r = handle_[i];
            r.translate(rect_.topLeft());
            if (handleHover_ == i) {
                painter->setPen(Qt::blue);
                painter->setBrush(QColor(164, 219, 255));
            }
            else {
                painter->setPen(Qt::blue);
                painter->setBrush(Qt::blue);
            }

            painter->drawRect(r);
        }
    }
}

void MvQAreaSelection::mousePressEventFromView(QMouseEvent* event)
{
    checkHandleSize();

    if (!activated_ || !acceptMouseEvents_)
        return;

    //
    handleHover_ = -1;

    if (rect_.isNull()) {
        // Get scene position
        zoomRectOrigin_ = mapFromScene(plotView_->mapToScene(event->pos()));

        if ((zoomLayout_ = plotScene_->findProjectorItem(mapToScene(zoomRectOrigin_))) == nullptr) {
            currentAction_ = NoAction;
        }
        else {
            currentAction_ = DefineAction;
            rect_.setRect(zoomRectOrigin_.x(), zoomRectOrigin_.y(), 0, 0);
            setVisible(true);
        }
    }

    else {
        float dh = handleSize_ / 2;
        QRectF cRect = controlRect();

        // qDebug() << "crect" << cRect;

        dragPos_ = mapFromScene(plotView_->mapToScene(event->pos()));

        if ((zoomLayout_ = plotScene_->findProjectorItem(mapToScene(cRect.center()))) != nullptr &&
            cRect.adjusted(-dh, -dh, dh, dh).contains(dragPos_)) {
            QPointF xp = dragPos_ - cRect.topLeft();

            // qDebug() << "xp" << dragPos_<< xp << rect_.topLeft();

            updateHandles();

            resizeId_ = -1;
            for (int i = 0; i < handle_.count(); i++) {
                // qDebug() << "handle" << i << handle_[i];
                // If a handle is selected it starts a resize action
                if (handle_[i].contains(xp)) {
                    resizeId_ = i;

                    Qt::CursorShape shape = Qt::ArrowCursor;

                    switch (resizeId_) {
                        case 0:
                            shape = Qt::SizeBDiagCursor;
                            break;
                        case 1:
                            shape = Qt::SizeHorCursor;
                            break;
                        case 2:
                            shape = Qt::SizeFDiagCursor;
                            break;
                        case 3:
                            shape = Qt::SizeVerCursor;
                            break;
                        case 4:
                            shape = Qt::SizeBDiagCursor;
                            break;
                        case 5:
                            shape = Qt::SizeHorCursor;
                            break;
                        case 6:
                            shape = Qt::SizeFDiagCursor;
                            break;
                        case 7:
                            shape = Qt::SizeVerCursor;
                            break;
                        default:
                            break;
                    }

                    setCursor(QCursor(shape));
                    currentAction_ = ResizeAction;

                    // qDebug() << "resizeId" << resizeId_;
                    return;
                }
            }

            // If no resize action it checks if the drag point is
            // in the selection rentangle and starts a move action
            if (resizeId_ == -1 && cRect.contains(dragPos_)) {
                currentAction_ = MoveAction;
                setCursor(QCursor(Qt::SizeAllCursor));
            }
            // Otherwise unselect
            else {
                currentAction_ = NoAction;
                dragPos_ = QPointF();
                if (showHandles_) {
                    showHandles_ = false;
                    update();
                }
            }
        }
        else {
            currentAction_ = NoAction;
            dragPos_ = QPointF();

            if (showHandles_) {
                showHandles_ = false;
                update();
            }
        }
    }
}

void MvQAreaSelection::mouseMoveEventFromView(QMouseEvent* event)
{
    if (!activated_ || !acceptMouseEvents_)
        return;

    // qDebug() << "mouseMove in area";

    QPointF pos = mapFromScene(plotView_->mapToScene(event->pos()));

    if (currentAction_ == DefineAction) {
        QPointF dp = pos - zoomRectOrigin_;

        if (dp.x() <= 0. || dp.y() >= 0.)
            return;

        QRectF r = updateControlRect(currentAction_, dp);
        if (r.isEmpty()) {
            return;
        }

        prepareGeometryChange();
        updateItemByControlRect(r);
        update();
    }
    else if (currentAction_ == ResizeAction) {
        QPointF dp = pos - dragPos_;

        // Current control rect
        QRectF rOri = controlRect();
        QRectF r = updateControlRect(currentAction_, dp);

        if (r.isEmpty() ||
            fabs(r.width()) <= 8. || fabs(r.height()) <= 8. ||
            r.width() < 0. || r.height() < 0.) {
            dragPos_ = pos;
            return;
        }

        if (r.width() < rOri.width() || r.height() < rOri.height()) {
            QRectF plotRect = mapRectFromScene(zoomLayout_->mapToScene(zoomLayout_->boundingRect()).boundingRect());
            if (!plotRect.contains(pos)) {
                dragPos_ = pos;
                return;
            }
        }


        prepareGeometryChange();
        updateItemByControlRect(r);
        dragPos_ = pos;
        showHandles_ = false;
        update();
    }
    else if (currentAction_ == MoveAction) {
        QPointF dp = pos - dragPos_;

        // qDebug() << "move" << dp << rect_;


        QRectF r = updateControlRect(currentAction_, dp);
        if (r.isEmpty()) {
            return;
        }

        prepareGeometryChange();
        updateItemByControlRect(r);
        dragPos_ = pos;
        showHandles_ = false;
        update();
    }
    else if (event->buttons() == Qt::NoButton) {
        if (showHandles_ == false)
            return;

        QRectF r = controlRect();  // controlRect
        QPointF dp = pos - r.topLeft();

        updateHandles();
        int oriHover = handleHover_;
        handleHover_ = -1;
        for (int i = 0; i < handle_.count(); i++) {
            if (handle_[i].contains(dp)) {
                handleHover_ = i;
                if (handleHover_ != oriHover) {
                    update();
                }
                break;
            }
        }

        if (oriHover != -1) {
            update();
        }
    }
}

void MvQAreaSelection::mouseReleaseEventFromView(QMouseEvent* event)
{
    if (!activated_ || !acceptMouseEvents_)
        return;

    QPointF pos = mapFromScene(plotView_->mapToScene(event->pos()));

    if (currentAction_ == DefineAction) {
        QPointF dp = pos - zoomRectOrigin_;
        if (dp.x() <= 0. || dp.y() >= 0.) {
            clearArea();
            return;
        }

        QRectF r = updateControlRect(currentAction_, dp);
        if (r.isEmpty() || fabs(r.width()) < 8. || fabs(r.height()) < 8.) {
            clearArea();
            return;
        }

        prepareGeometryChange();
        updateItemByControlRect(r);
        update();

        QPointF gp1, gp2;
        getAreaGeoCorners(gp1, gp2);

        currentAction_ = NoAction;
        zoomLayout_ = nullptr;
        coordPoint_.clear();
        coordPoint_ << gp1 << gp2;

        // blLat,blLon,trLat,trLon
        emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
    }
    else if (currentAction_ == ResizeAction) {
        QPointF dp = pos - dragPos_;

        QRectF r = updateControlRect(currentAction_, dp);

        if (r.isEmpty() ||
            fabs(r.width()) < 8. || fabs(r.height()) < 8. ||
            r.width() < 0. || r.height() < 0.) {
            r = rect_;
            // clearArea();
            // return;
        }

        prepareGeometryChange();
        updateItemByControlRect(r);
        // update();

        QPointF gp1, gp2;
        getAreaGeoCorners(gp1, gp2);

        currentAction_ = NoAction;
        zoomLayout_ = nullptr;
        coordPoint_.clear();
        coordPoint_ << gp1 << gp2;
        dragPos_ = QPointF();

        // blLat,blLon,trLat,trLon
        emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());

        updateHandles();
        showHandles_ = true;
        update();
        unsetCursor();
    }
    else if (currentAction_ == MoveAction) {
        QPointF dp = pos - dragPos_;

        QRectF r = updateControlRect(currentAction_, dp);
        if (r.isEmpty()) {
            return;
        }

        prepareGeometryChange();
        updateItemByControlRect(r);

        QPointF gp1, gp2;
        getAreaGeoCorners(gp1, gp2);

        currentAction_ = NoAction;
        zoomLayout_ = nullptr;
        coordPoint_.clear();
        coordPoint_ << gp1 << gp2;
        dragPos_ = QPointF();

        // blLat,blLon,trLat,trLon
        emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());

        updateHandles();
        showHandles_ = true;
        update();
        unsetCursor();
    }
}


void MvQAreaSelection::setArea(double blLat, double blLon, double trLat, double trLon)
{
    // double west, double north, double east, double south)
    QPointF sp1, sp2;

    if (rect_.isEmpty()) {
        if ((zoomLayout_ = plotScene_->firstProjectorItem()) == nullptr) {
            return;
        }
    }
    else {
        sp1.setX(rect_.x());
        sp1.setY(rect_.y());
        if ((zoomLayout_ = plotScene_->findProjectorItem(mapToScene(sp1))) == nullptr) {
            return;
        }
    }

    QPointF gp1(blLon, blLat);
    QPointF gp2(trLon, trLat);

    if (zoomLayout_->containsGeoCoords(gp1) &&
        zoomLayout_->containsGeoCoords(gp2)) {
        zoomLayout_->mapFromGeoToSceneCoords(gp1, sp1);
        zoomLayout_->mapFromGeoToSceneCoords(gp2, sp2);

        sp1 = mapFromScene(sp1);
        sp2 = mapFromScene(sp2);

        // qDebug() << gp1 << gp2 << sp1 << sp2;

        QRectF r = QRectF(sp1.x(), sp1.y(), sp2.x() - sp1.x(), sp2.y() - sp1.y());
        if (r.width() < 0 || r.height() < 0) {
            if (rect_.isEmpty() == false) {
                getAreaGeoCorners(gp1, gp2);
                // blLat,blLon,trLat,trLon
                emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
            }
            return;
        }
        // qDebug() << rect_;

        coordPoint_.clear();
        coordPoint_ << gp1 << gp2;
        prepareGeometryChange();
        rect_ = r;
        updateHandles();
        update();
    }
    else {
        QPointF gp1, gp2;
        getAreaGeoCorners(gp1, gp2);
        // blLat,blLon,trLat,trLon
        emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
    }
}

void MvQAreaSelection::clearArea()
{
    currentAction_ = NoAction;
    prepareGeometryChange();
    rect_ = QRectF();
    coordPoint_.clear();
    showHandles_ = false;
    update();
    emit areaIsUndefined();
}


// It should be called only after zoom!!!
// If the projection is changed the area should be set "undefined"!!

void MvQAreaSelection::reset()
{
    if (coordPoint_.isEmpty() || rect_.isNull())
        return;

    // We need to find the projectorItem!!
    QPointF sp0, sp1;

    if ((zoomLayout_ = plotScene_->firstProjectorItem()) == nullptr) {
        clearArea();
        return;
    }

    QPointF gp0 = coordPoint_[0];
    QPointF gp1 = coordPoint_[1];

    // qDebug() << coordPoint_;

    if (zoomLayout_->containsGeoCoords(gp0) &&
        zoomLayout_->containsGeoCoords(gp1)) {
        zoomLayout_->mapFromGeoToSceneCoords(gp0, sp0);
        zoomLayout_->mapFromGeoToSceneCoords(gp1, sp1);

        sp0 = mapFromScene(sp0);
        sp1 = mapFromScene(sp1);


        // qDebug() << sp0 << sp1;

        prepareGeometryChange();
        rect_ = QRectF(sp0.x(), sp0.y(), sp1.x() - sp0.x(), sp1.y() - sp0.y());
        updateHandles();
        update();
    }
    else {
        clearArea();
    }
}

void MvQAreaSelection::selectAllArea()
{
    if ((zoomLayout_ = plotScene_->firstProjectorItem()) != nullptr) {
        prepareGeometryChange();
        rect_ = mapRectFromScene(zoomLayout_->mapRectToScene(zoomLayout_->boundingRect()));

        QPointF gp1, gp2;
        getAreaGeoCorners(gp1, gp2);  // this requires zoomLayout_ !!!
        coordPoint_.clear();
        coordPoint_ << gp1 << gp2;

        currentAction_ = NoAction;
        zoomLayout_ = nullptr;
        dragPos_ = QPointF();

        showHandles_ = false;
        update();

        // blLat,blLon,trLat,trLon
        emit areaIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
    }
}

void MvQAreaSelection::updateHandles()
{
    QRectF r = controlRect();

    checkHandleSize();

    float dh = handleSize_ / 2;
    float w = r.width();
    float h = r.height();

    // Check handles!!!
    handle_[0].moveTo(-dh, -dh);
    handle_[1].moveTo(-dh, h / 2. - dh);
    handle_[2].moveTo(-dh, h - dh);
    handle_[3].moveTo(w / 2. - dh, h - dh);
    handle_[4].moveTo(w - dh, h - dh);
    handle_[5].moveTo(w - dh, h / 2. - dh);
    handle_[6].moveTo(w - dh, -dh);
    handle_[7].moveTo(w / 2. - dh, -dh);
}


void MvQAreaSelection::setActivated(bool b)
{
    if (showHandles_ == true) {
        showHandles_ = false;
        update();
    }
    activated_ = b;
}


QRectF MvQAreaSelection::updateControlRect(CurrentAction action, QPointF& dp)
{
    // Current control rect
    QRectF r = controlRect();

    if (!zoomLayout_) {
        return {};
    }

    // Plot area in local coordinates
    QRectF plotRect = mapRectFromScene(zoomLayout_->mapToScene(zoomLayout_->boundingRect()).boundingRect());

    if (action == DefineAction) {
        r = QRectF(zoomRectOrigin_.x(), zoomRectOrigin_.y() + dp.y(), dp.x(), -dp.y());

        if (r.right() > plotRect.right()) {
            dp.setX(dp.x() - (r.right() - plotRect.right()));
        }
        else if (r.left() < plotRect.left()) {
            dp.setX(dp.x() + plotRect.left() - r.left());
        }
        if (r.bottom() > plotRect.bottom()) {
            dp.setY(dp.y() - (r.bottom() - plotRect.bottom()));
        }
        else if (r.top() < plotRect.top()) {
            dp.setY(dp.y() + plotRect.top() - r.top());
        }

        r = QRectF(zoomRectOrigin_.x(), zoomRectOrigin_.y() + dp.y(), dp.x(), -dp.y());
    }
    else if (action == ResizeAction) {
        r = rect_;

        switch (resizeId_) {
            case 0:
                r.adjust(dp.x(), dp.y(), 0, 0);
                break;
            case 1:
                r.adjust(dp.x(), 0, 0, 0);
                break;
            case 2:
                r.adjust(dp.x(), 0, 0, dp.y());
                break;
            case 3:
                r.adjust(0, 0, 0, dp.y());
                break;
            case 4:
                r.adjust(0, 0, dp.x(), dp.y());
                break;
            case 5:
                r.adjust(0, 0, dp.x(), 0);
                break;
            case 6:
                r.adjust(0, dp.y(), dp.x(), 0);
                break;
            case 7:
                r.adjust(0, dp.y(), 0, 0);
                break;
            default:
                break;
        }

        if (r.width() < 0) {
            // r.setWidth(8.);
            r = rect_;
        }
        if (r.height() < 0) {
            // r.setHeight(8.);
            r = rect_;
        }

        // qDebug() << "RESIZE" << rect_ << r;

        r = plotRect.intersected(r);
    }
    else if (action == MoveAction) {
        r = rect_.translated(dp);

        if (r.right() > plotRect.right()) {
            dp.setX(dp.x() - (r.right() - plotRect.right()));
        }
        else if (r.left() < plotRect.left()) {
            dp.setX(dp.x() + plotRect.left() - r.left());
        }
        if (r.bottom() > plotRect.bottom()) {
            dp.setY(dp.y() - (r.bottom() - plotRect.bottom()));
        }
        else if (r.top() < plotRect.top()) {
            dp.setY(dp.y() + plotRect.top() - r.top());
        }

        r = rect_.translated(dp);
    }


    QPointF p1 = mapToScene(r.topLeft());
    QPointF p2 = mapToScene(r.bottomRight());

    if (!zoomLayout_->containsSceneCoords(p1) ||
        !zoomLayout_->containsSceneCoords(p2)) {
        // qDebug() << "false";
        return {};
    }

    return r;
}

void MvQAreaSelection::updateItemByControlRect(QRectF r)
{
    rect_ = r;
}

QRectF MvQAreaSelection::controlRect()
{
    return rect_;
}

void MvQAreaSelection::getAreaGeoCorners(QPointF& gp1, QPointF& gp2)
{
    if (!zoomLayout_)
        return;

    // Get geo coordinates of the bl and tr rect corners on the map.
    // The y axis is inverted!! So we need tl and br corners of the
    // qt rectangle!!!!!!!
    QPointF sp1 = mapToScene(rect_.topLeft());
    QPointF sp2 = mapToScene(rect_.bottomRight());
    zoomLayout_->mapFromSceneToGeoCoords(sp1, gp1);
    zoomLayout_->mapFromSceneToGeoCoords(sp2, gp2);
}

void MvQAreaSelection::checkHandleSize() const
{
    handleSize_ = physicalHandleSize_ / parentItem()->scale();
    for (int i = 0; i < 8; i++) {
        handle_[i].setWidth(handleSize_);
        handle_[i].setHeight(handleSize_);
    }
}
