/***************************** 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 <cmath>

#include <QDebug>
#include <QMouseEvent>
#include <QPainter>

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

MvQLineSelection::MvQLineSelection(MgQPlotScene* scene, MvQPlotView* view, QGraphicsItem* parent) :
    MvQPlotItem(scene, view, parent)
{
    pen_ = QPen(QColor(0, 0, 255, 230), 2);
    pen_.setCosmetic(true);
    // pen_=QPen(QColor(103,140,168),2);
    brush_ = QBrush(QColor(164, 219, 255, 180));
    currentAction_ = NoAction;
    zoomLayout_ = nullptr;
    line_ = QLine();

    arrowPen_ = QPen(QColor(0, 0, 0));
    arrowPen_.setCosmetic(true);

    // arrowBrush_=QBrush(QColor(103,140,168));
    arrowBrush_ = QBrush(QColor(0, 0, 255));

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

    showHandles_ = false;
    handleHover_ = -1;

    arrowSize_ = 10.;
}

MvQLineSelection::~MvQLineSelection() = default;

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

    qreal x1 = line_.p1().x();
    qreal y1 = line_.p1().y();
    qreal x2 = line_.p2().x();
    qreal y2 = line_.p2().y();

    qreal tlx, tly, brx, bry;

    tlx = (x1 <= x2) ? x1 : x2;
    tly = (y1 < y2) ? y1 : y2;
    brx = (x1 <= x2) ? x2 : x1;
    bry = (y1 < y2) ? y2 : y1;

    QRectF r(QPointF(tlx, tly), QPointF(brx, bry));

    if (r.height() < 10.) {
        r.adjust(0., -5., 0., 5.);
    }
    else if (r.width() < 10.) {
        r.adjust(-5., 0., 5., 0.);
    }

    float d = (handleSize_ / 2. > arrowSize_) ? handleSize_ / 2. : arrowSize_;

    return r.adjusted(-d / 2., -d / 2., d / 2., d / 2.);
}

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

    /*painter->setBrush(startBrush_);
    painter->drawEllipse(QRectF(line_.p1().x()-5,line_.p1().y()-5,
                10,10));

    painter->setBrush(endBrush_);
    painter->drawEllipse(QRectF(line_.p2().x()-5,line_.p2().y()-5,
                10,10));
    painter->setBrush(QBrush());*/

    painter->setRenderHint(QPainter::Antialiasing);
    painter->setBrush(QBrush());

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

    // Draw arrow
    QPolygonF arrow;
    QPointF cp((line_.p1().x() + line_.p2().x()) / 2.,
               (line_.p1().y() + line_.p2().y()) / 2.);

    arrow << QPointF(0., 0.) << QPointF(-arrowSize_, arrowSize_ / 2.) << QPointF(-arrowSize_, -arrowSize_ / 2.);

    // arrow.translate(cp);

    float angle = atan2f(line_.p2().y() - line_.p1().y(),
                         line_.p2().x() - line_.p1().x());

    angle = angle * 180. / M_PI;

    // qDebug() << "angle" << angle*180./M_PI;

    painter->setPen(arrowPen_);
    painter->setBrush(arrowBrush_);

    painter->save();
    painter->translate(cp);
    painter->rotate(angle);
    painter->drawPolygon(arrow);
    painter->restore();

    painter->setRenderHint(QPainter::Antialiasing, false);

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

            painter->drawRect(r);
        }
    }
}

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

    if (!activated_ || !acceptMouseEvents_)
        return;

    handleHover_ = -1;

    if (line_.isNull()) {
        // Get item position
        lineOrigin_ = mapFromScene(plotView_->mapToScene(event->pos()));

        if ((zoomLayout_ = plotScene_->findProjectorItem(mapToScene(lineOrigin_))) == nullptr) {
            currentAction_ = NoAction;
        }
        else {
            currentAction_ = DefineAction;

            line_.setPoints(lineOrigin_, lineOrigin_);
            setVisible(true);
        }
    }

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

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

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

        if ((zoomLayout_ = plotScene_->firstProjectorItem()) != nullptr &&
            checkPointInLineArea(dragPos_) == true) {
            QPointF xp = dragPos_ - line_.p1();
            updateHandles();

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

                    Qt::CursorShape shape;
                    shape = Qt::SizeBDiagCursor;
                    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) {
                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 MvQLineSelection::mouseMoveEventFromView(QMouseEvent* event)
{
    if (!activated_ || !acceptMouseEvents_)
        return;

    // qDebug() << "mouseMove in area";
    QPointF pos = mapFromScene(plotView_->mapToScene(event->pos()));

    if (currentAction_ == DefineAction) {
        QPointF p2 = pos;
        checkEndPoint(p2);
        if (p2.isNull()) {
            clearLine();
            return;
        }

        prepareGeometryChange();
        line_ = QLineF(lineOrigin_, p2);
        update();
    }
    else if (currentAction_ == ResizeAction) {
        QPointF dp = pos - dragPos_;

        QPointF p = dp;
        if (resizeId_ == 0)
            p += line_.p1();
        else if (resizeId_ == 1)
            p += line_.p2();

        checkEndPoint(p);

        QLineF ln;
        if (resizeId_ == 0)
            ln = QLineF(p, line_.p2());
        else if (resizeId_ == 1)
            ln = QLineF(line_.p1(), p);

        prepareGeometryChange();
        line_ = ln;
        dragPos_ = pos;
        showHandles_ = false;
        update();
    }
    else if (currentAction_ == MoveAction) {
        QPointF dp = pos - dragPos_;
        checkMoveDelta(dp);

        prepareGeometryChange();
        line_.translate(dp);
        dragPos_ = pos;
        showHandles_ = false;
        update();
    }
    else if (event->buttons() == Qt::NoButton) {
        if (showHandles_ == false)
            return;

        QPointF dp = pos - line_.p1();

        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 MvQLineSelection::mouseReleaseEventFromView(QMouseEvent* event)
{
    if (!activated_ || !acceptMouseEvents_)
        return;

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

    if (currentAction_ == DefineAction) {
        QPointF p2 = pos;
        checkEndPoint(p2);
        if (p2.isNull() ||
            QLineF(lineOrigin_, p2).length() < 8.) {
            clearLine();
            return;
        }

        prepareGeometryChange();
        line_ = QLineF(lineOrigin_, p2);
        update();

        // Get geo coordinates of rect corners
        QPointF sp1 = line_.p1();
        QPointF sp2 = line_.p2();
        QPointF gp1, gp2;
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp1), gp1);
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp2), gp2);

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

        emit lineIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
    }
    else if (currentAction_ == ResizeAction) {
        QPointF dp = pos - dragPos_;

        QPointF p = dp;
        if (resizeId_ == 0)
            p += line_.p1();
        else if (resizeId_ == 1)
            p += line_.p2();

        checkEndPoint(p);

        QLineF ln;
        if (resizeId_ == 0)
            ln = QLineF(p, line_.p2());
        else if (resizeId_ == 1)
            ln = QLineF(line_.p1(), p);

        if (ln.isNull() || ln.length() < 8.) {
            clearLine();
            return;
        }

        prepareGeometryChange();
        line_ = ln;
        // update();

        // Get geo coordinates of rect corners
        QPointF sp1 = line_.p1();
        QPointF sp2 = line_.p2();
        QPointF gp1, gp2;
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp1), gp1);
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp2), gp2);

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

        emit lineIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());

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

        checkMoveDelta(dp);

        prepareGeometryChange();
        line_.translate(dp);

        QPointF sp1 = line_.p1();
        QPointF sp2 = line_.p2();
        QPointF gp1, gp2;
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp1), gp1);
        zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp2), gp2);

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

        emit lineIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());

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


void MvQLineSelection::setLine(double lat1, double lon1, double lat2, double lon2)
{
    // double west, double north, double east, double south)

    double x1 = lon1;
    double y1 = lat1;
    double x2 = lon2;
    double y2 = lat2;

    QPointF sp0, sp1;

    if (line_.isNull() || line_.p1() == line_.p2()) {
        if ((zoomLayout_ = plotScene_->firstProjectorItem()) == nullptr) {
            return;
        }
    }
    else {
        sp0 = line_.p1();
        if ((zoomLayout_ = plotScene_->findProjectorItem(mapToScene(sp0))) == nullptr) {
            return;
        }
    }


    QPointF gp0(x1, y1);
    QPointF gp1(x2, y2);

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

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

        prepareGeometryChange();
        line_.setPoints(sp0, sp1);
        coordPoint_.clear();
        coordPoint_ << gp0 << gp1;
        updateHandles();
        update();
    }
    else {
        QPointF sp1 = line_.p1();
        QPointF sp2 = line_.p2();

        sp0 = mapToScene(sp0);
        sp1 = mapToScene(sp1);

        QPointF gp1, gp2;
        zoomLayout_->mapFromSceneToGeoCoords(sp1, gp1);
        zoomLayout_->mapFromSceneToGeoCoords(sp2, gp2);

        emit lineIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());
    }
}

void MvQLineSelection::clearLine()
{
    currentAction_ = NoAction;
    prepareGeometryChange();
    line_ = QLineF();
    coordPoint_.clear();
    showHandles_ = false;
    update();
    emit lineIsUndefined();
}


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

void MvQLineSelection::reset()
{
    if (coordPoint_.isEmpty() || line_.isNull())
        return;

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

    if ((zoomLayout_ = plotScene_->firstProjectorItem()) == nullptr) {
        clearLine();
        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);

        prepareGeometryChange();
        line_ = QLineF(sp0, sp1);
        coordPoint_.clear();
        coordPoint_ << gp0 << gp1;
        update();
    }
    else {
        clearLine();
    }
}

void MvQLineSelection::changeDirection()
{
    if (coordPoint_.isEmpty() || line_.isNull())
        return;

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

    QPointF sp1 = line_.p2();
    QPointF sp2 = line_.p1();

    QPointF gp1, gp2;

    zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp1), gp1);
    zoomLayout_->mapFromSceneToGeoCoords(mapToScene(sp2), gp2);

    prepareGeometryChange();
    line_ = QLineF(sp1, sp2);
    coordPoint_.clear();
    coordPoint_ << gp1 << gp2;

    emit lineIsDefined(gp1.y(), gp1.x(), gp2.y(), gp2.x());

    updateHandles();
    update();
}

void MvQLineSelection::updateHandles()
{
    checkHandleSize();

    float dh = handleSize_ / 2.;
    float w = line_.p2().x() - line_.p1().x();
    float h = line_.p2().y() - line_.p1().y();
    handle_[0].moveTo(-dh, -dh);
    handle_[1].moveTo(w - dh, h - dh);
}


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

void MvQLineSelection::checkEndPoint(QPointF& p2)
{
    if (!zoomLayout_) {
        p2 = QPointF();
        return;
    }

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

    if (p2.x() > plotRect.right()) {
        p2.setX(plotRect.right());
    }
    else if (p2.x() < plotRect.left()) {
        p2.setX(plotRect.left());
    }
    if (p2.y() > plotRect.bottom()) {
        p2.setY(plotRect.bottom());
    }
    else if (p2.y() < plotRect.top()) {
        p2.setY(plotRect.top());
    }
}

void MvQLineSelection::checkMoveDelta(QPointF& dp)
{
    if (!zoomLayout_) {
        dp = QPointF();
    }

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

    // qDebug() << "delta" << dp;

    QLineF r = line_.translated(dp);

    // qDebug() << "translated" << r;

    QPointF p1 = r.p1();
    checkEndPoint(p1);

    QPointF delta1 = p1 - r.p1();

    // qDebug() << "1 p/delta" << p1 << delta1;

    r.translate(delta1);

    dp = dp + delta1;

    // qDebug() << r << dp;

    QPointF p2 = r.p2();
    checkEndPoint(p2);
    QPointF delta2 = p2 - r.p2();

    // qDebug() << "2 p/delta" << p2 << delta2;

    r.translate(p2 - r.p2());

    dp = dp + delta2;

    // qDebug() << "dp" << dp;
}

bool MvQLineSelection::checkPointInLineArea(QPointF& p)
{
    double maxd = 20.;

    QPointF p1 = line_.p1();
    QPointF p2 = line_.p2();

    QPointF r = p2 - p1;
    QPointF d = p - p1;

    double rlen = sqrt(r.x() * r.x() + r.y() * r.y());
    double dlen = sqrt(d.x() * d.x() + d.y() * d.y());

    // scalar product
    double sp = d.x() * r.x() + d.y() * r.y();

    double dp;
    if (sp == 0.)
        dp = dlen;
    else {
        double dpr = sp / rlen;
        dp = sqrt(dlen * dlen - dpr * dpr);
    }

    if (fabs(dp) < maxd) {
        QPointF d2 = p - p2;
        double lsp = d.x() * d2.x() + d.y() * d2.y();
        double d2len = sqrt(d2.x() * d2.x() + d2.y() * d2.y());
        if (lsp > 0 && dlen > maxd && d2len > maxd) {
            return false;
        }
        return true;
    }
    else {
        return false;
    }
}

void MvQLineSelection::checkHandleSize() const
{
    handleSize_ = physicalHandleSize_ / parentItem()->scale();
    arrowSize_ = 10. / parentItem()->scale();

    for (int i = 0; i < 2; i++) {
        handle_[i].setWidth(handleSize_);
        handle_[i].setHeight(handleSize_);
    }
}
