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

 Copyright 2021 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 "MvQFeatureSelector.h"

#include <algorithm>
#include <typeinfo>

#include <QApplication>
#include <QAction>
#include <QtAlgorithms>
#include <QClipboard>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QStyle>

#include "MvQPlotView.h"
#include "MvQFeatureCommand.h"
#include "MvQFeatureItem.h"
#include "MvQFeatureCommandTarget.h"
#include "MvQMethods.h"

//#define MVQFEATURESELECTORPOINT_DEBUG_
//#define MVQFEATURESELECTORITEM_DEBUG_

//========================================
//
// MvQFeatureSelectorPoint
//
//========================================

class MvQFeatureSelectorPoint : public QGraphicsItem
{
public:
    MvQFeatureSelectorPoint(MvQFeatureSelector*, QString pixName, QString hoverPixName,
                            double xDeltaFactor, double yDeltaFactor);
    QRectF boundingRect() const;
    void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*);
    virtual void updatePosition() = 0;

protected:
    QVariant itemChange(GraphicsItemChange, const QVariant&);
    void mousePressEvent(QGraphicsSceneMouseEvent*);
    void mouseMoveEvent(QGraphicsSceneMouseEvent*);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
    void hoverEnterEvent(QGraphicsSceneHoverEvent*);
    void hoverLeaveEvent(QGraphicsSceneHoverEvent*);
    virtual void handleMouseMove(QPointF ep, QRectF& r) = 0;

    MvQFeatureSelector* selector_;
    QRectF bRect_;
    bool hover_{false};
    QBrush brush_;
    QBrush hoverBrush_;
    QPixmap pix_;
    QPixmap pixHover_;
    QPointF dragPos_;
};

class MvQTopLeftFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQTopLeftFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQTopMidFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQTopMidFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQTopRightFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQTopRightFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQRightMidFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQRightMidFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQBottomRightFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQBottomRightFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQBottomMidFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQBottomMidFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQBottomLeftFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQBottomLeftFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

class MvQLeftMidFeatureSelectorPoint : public MvQFeatureSelectorPoint
{
public:
    MvQLeftMidFeatureSelectorPoint(MvQFeatureSelector*);
    void updatePosition() override;

protected:
    void handleMouseMove(QPointF ep, QRectF& r) override;
};

//========================================
//
// MvQFeatureSelectorPoint
//
//========================================

MvQFeatureSelectorPoint::MvQFeatureSelectorPoint(MvQFeatureSelector* selector, QString pixName, QString hoverPixName,
                                                 double xDeltaFactor, double yDeltaFactor) :
    selector_(selector)
{
    // The pixmap is always twice as large as the itemrect so that we can allow for the
    // scaling in the plot window (it can go up to 200% at the moment)
    int rSize     = 12;
    int pixFactor = 4;
    pix_          = MvQ::makePixmap(pixName, pixFactor * rSize, pixFactor * rSize);
    pixHover_     = MvQ::makePixmap(hoverPixName, pixFactor * rSize, pixFactor * rSize);

    bRect_       = QRectF(0, 0, rSize, rSize);
    double delta = rSize / 2;
    bRect_.moveTo(xDeltaFactor * delta, yDeltaFactor * delta);

    setFlag(ItemIsMovable);
    setFlag(ItemSendsGeometryChanges);
    setAcceptHoverEvents(true);

    setParentItem(selector_);
}

QRectF MvQFeatureSelectorPoint::boundingRect() const
{
    return bRect_;
}

void MvQFeatureSelectorPoint::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    if (!hover_)
        painter->drawPixmap(bRect_, pix_, pix_.rect());
    else
        painter->drawPixmap(bRect_, pixHover_, pixHover_.rect());
}

QVariant MvQFeatureSelectorPoint::itemChange(GraphicsItemChange change, const QVariant& value)
{
    return QGraphicsItem::itemChange(change, value);
}

void MvQFeatureSelectorPoint::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    update();
    QGraphicsItem::mousePressEvent(event);
    dragPos_ = mapToScene(event->pos());
    selector_->controlPointPressed(this);
}

void MvQFeatureSelectorPoint::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    QRectF r = selector_->boundingRect();
    handleMouseMove(event->pos(), r);
    selector_->controlPointMoved(this, r);
}

void MvQFeatureSelectorPoint::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    QGraphicsItem::mouseReleaseEvent(event);
    dragPos_ = QPointF();
    selector_->controlPointReleased(this);
}

void MvQFeatureSelectorPoint::hoverEnterEvent(QGraphicsSceneHoverEvent*)
{
    hover_ = true;
    update();
}

void MvQFeatureSelectorPoint::hoverLeaveEvent(QGraphicsSceneHoverEvent*)
{
    hover_ = false;
    update();
}

//========================================
//
// MvQTopLeftFeatureSelectorPoint
//
//========================================

MvQTopLeftFeatureSelectorPoint::MvQTopLeftFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_bdiag.svg", ":/uPlot/cursor_bdiag_hover.svg", -2, -2)
{
    updatePosition();
}

void MvQTopLeftFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.topLeft());
}

void MvQTopLeftFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (selector_->keepAspectRatio()) {
        QPointF dp = mapToScene(ep) - dragPos_;
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << "(topLeft) handleMouseMove ------>";
        qDebug() << " event pos in parent:" << mapToParent(ep) << "in scene:" << mapToScene(ep) << "dragPos_ in scene" << dragPos_;
        qDebug() << " event pos change in scene:" << dp;
        qDebug() << " topLeft:" << r.topLeft() << "in scene:" << selector_->mapToScene(r.topLeft());
#endif
        dragPos_ = mapToScene(ep);
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " selector rect" << r;
        qDebug() << " item pos in scene" << selector_->mapToScene(pos());
#endif
        // dp is in scene coordinates!
        double eps = 1E-4;
        if (fabs(dp.x()) < eps && fabs(dp.y()) < eps) {
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
            qDebug() << " --> NO change";
#endif
            r = QRectF();
            return;
        }

        auto ratio    = fabs(r.height() / r.width());
        double x      = dp.x();
        double y      = dp.y();
        bool grow     = !(x >= 0. && y >= 0.);
        double factor = (grow) ? -1.0 : 1.0;
        if (fabs(x) >= fabs(y)) {
            x = factor * fabs(x);
            y = factor * fabs(x) * ratio;
        }
        else {
            y = factor * fabs(y);
            x = factor * fabs(y) / ratio;
        }
        r.setTopLeft(r.topLeft() + QPointF(x, y));
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " grow:" << grow << "ratio:" << ratio << "delta:" << QPointF(x, y) << "topLeft:" << r.topLeft();
#endif
    }
    else {
        r.setTopLeft(mapToParent(ep));
    }
}

//========================================
//
// MvQTopMidFeatureSelectorPoint
//
//========================================

MvQTopMidFeatureSelectorPoint::MvQTopMidFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_vert.svg", ":/uPlot/cursor_vert_hover.svg", -1, -2)
{
    updatePosition();
}

void MvQTopMidFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.center().x(), r.y());
}

void MvQTopMidFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (!selector_->keepAspectRatio()) {
        auto pp = mapToParent(ep);
        setPos(QPointF(r.center().x(), pp.y()));
        r.setTop(pos().y());
    }
}

//========================================
//
// MvQTopRightFeatureSelectorPoint
//
//========================================

MvQTopRightFeatureSelectorPoint::MvQTopRightFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_fdiag.svg", ":/uPlot/cursor_fdiag_hover.svg", 0, -2)
{
    updatePosition();
}

void MvQTopRightFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.left() + r.width(), r.top());
}

void MvQTopRightFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (selector_->keepAspectRatio()) {
        QPointF dp = mapToScene(ep) - dragPos_;
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << "(topRight) handleMouseMove ------>";
        qDebug() << " event pos in parent:" << mapToParent(ep) << "in scene:" << mapToScene(ep) << "dragPos_ in scene" << dragPos_;
        qDebug() << " event pos change in scene:" << dp;
        qDebug() << " topRight:" << r.topRight() << "in scene:" << selector_->mapToScene(r.topRight());
#endif
        dragPos_ = mapToScene(ep);
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " selector rect" << r;
        qDebug() << " item pos in scene" << selector_->mapToScene(pos());
#endif
        // dp is in scene coordinates!
        double eps = 1E-4;
        if (fabs(dp.x()) < eps && fabs(dp.y()) < eps) {
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
            qDebug() << " --> NO change";
#endif
            r = QRectF();
            return;
        }
        auto ratio = fabs(r.height() / r.width());
        double x   = dp.x();
        double y   = dp.y();
        bool grow  = !(x >= 0. && y < 0.);
        if (fabs(x) >= fabs(y)) {
            if (grow) {
                x = -fabs(x);
                y = fabs(x) * ratio;
            }
            else {
                x = fabs(x);
                y = -fabs(x) * ratio;
            }
        }
        else {
            if (grow) {
                y = fabs(y);
                x = -fabs(y) / ratio;
            }
            else {
                y = -fabs(y);
                x = fabs(y) / ratio;
            }
        }

        r.setTopRight(r.topRight() + QPointF(x, y));
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " grow:" << grow << "ratio:" << ratio << "delta:" << QPointF(x, y) << "topRight:" << r.topRight();
#endif
    }
    else {
        r.setTopRight(mapToParent(ep));
    }
}

//========================================
//
// MvQRightMidFeatureSelectorPoint
//
//========================================

MvQRightMidFeatureSelectorPoint::MvQRightMidFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_horiz.svg", ":/uPlot/cursor_horiz_hover.svg", 0, -1)
{
    updatePosition();
}

void MvQRightMidFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.left() + r.width(), r.center().y());
}

void MvQRightMidFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (!selector_->keepAspectRatio()) {
        auto pp = mapToParent(ep);
        setPos(QPointF(pp.x(), r.center().y()));
        r.setRight(pos().x());
    }
}

//========================================
//
// MvQBottomRightFeatureSelectorPoint
//
//========================================

MvQBottomRightFeatureSelectorPoint::MvQBottomRightFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_bdiag.svg", ":/uPlot/cursor_bdiag_hover.svg", 0, 0)
{
    updatePosition();
}

void MvQBottomRightFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.left() + r.width(), r.top() + r.height());
}

void MvQBottomRightFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (selector_->keepAspectRatio()) {
        QPointF dp = mapToScene(ep) - dragPos_;
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << "(bottomRight) handleMouseMove ------>";
        qDebug() << " event pos in parent:" << mapToParent(ep) << "in scene:" << mapToScene(ep) << "dragPos_ in scene" << dragPos_;
        qDebug() << " event pos change in scene:" << dp;
        qDebug() << " bottomRight:" << r.bottomRight() << "in scene:" << selector_->mapToScene(r.bottomRight());
#endif
        dragPos_ = mapToScene(ep);
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " selector rect" << r;
        qDebug() << " item pos in scene" << selector_->mapToScene(pos());
#endif
        // dp is in scene coordinates!
        double eps = 1E-4;
        if (fabs(dp.x()) < eps && fabs(dp.y()) < eps) {
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
            qDebug() << " --> NO change";
#endif
            r = QRectF();
            return;
        }

        auto ratio    = fabs(r.height() / r.width());
        double x      = dp.x();
        double y      = dp.y();
        bool grow     = !(x >= 0. && y >= 0.);
        double factor = (grow) ? -1.0 : 1.0;
        if (fabs(x) >= fabs(y)) {
            x = factor * fabs(x);
            y = factor * fabs(x) * ratio;
        }
        else {
            y = factor * fabs(y);
            x = factor * fabs(y) / ratio;
        }
        r.setBottomRight(r.bottomRight() + QPointF(x, y));
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " grow:" << grow << "ratio:" << ratio << "delta:" << QPointF(x, y) << "bottomRight:" << r.bottomRight();
#endif
    }
    else {
        r.setBottomRight(mapToParent(ep));
    }
}

//========================================
//
// MvQBottomMidFeatureSelectorPoint
//
//========================================

MvQBottomMidFeatureSelectorPoint::MvQBottomMidFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_vert.svg", ":/uPlot/cursor_vert_hover.svg", -1, 0)
{
    updatePosition();
}

void MvQBottomMidFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.center().x(), r.top() + r.height());
}

void MvQBottomMidFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (!selector_->keepAspectRatio()) {
        auto pp = mapToParent(ep);
        setPos(QPointF(r.center().x(), pp.y()));
        r.setBottom(pos().y());
    }
}

//========================================
//
// MvQBottomLeftFeatureSelectorPoint
//
//========================================

MvQBottomLeftFeatureSelectorPoint::MvQBottomLeftFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_fdiag.svg", ":/uPlot/cursor_fdiag_hover.svg", -2, 0)
{
    updatePosition();
}

void MvQBottomLeftFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.left(), r.top() + r.height());
}

void MvQBottomLeftFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (selector_->keepAspectRatio()) {
        QPointF dp = mapToScene(ep) - dragPos_;
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << "(bottomLeft) handleMouseMove ------>";
        qDebug() << " event pos in parent:" << mapToParent(ep) << "in scene:" << mapToScene(ep) << "dragPos_ in scene" << dragPos_;
        qDebug() << " event pos change in scene:" << dp;
        qDebug() << " bottomLeft:" << r.bottomLeft() << "in scene:" << selector_->mapToScene(r.bottomLeft());
#endif
        dragPos_ = mapToScene(ep);
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " selector rect" << r;
        qDebug() << " item pos in scene" << selector_->mapToScene(pos());
#endif
        // dp is in scene coordinates!
        double eps = 1;
        if (fabs(dp.x()) < eps && fabs(dp.y()) < eps) {
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
            qDebug() << " --> NO change";
#endif
            r = QRectF();
            return;
        }

        auto ratio = fabs(r.height() / r.width());
        double x   = dp.x();
        double y   = dp.y();
        bool grow  = !(x >= 0. && y < 0.);
        if (fabs(x) >= fabs(y)) {
            if (grow) {
                x = -fabs(x);
                y = fabs(x) * ratio;
            }
            else {
                x = fabs(x);
                y = -fabs(x) * ratio;
            }
        }
        else {
            if (grow) {
                y = fabs(y);
                x = -fabs(y) / ratio;
            }
            else {
                y = -fabs(y);
                x = fabs(y) / ratio;
            }
        }
        r.setBottomLeft(r.bottomLeft() + QPointF(x, y));
#ifdef MVQFEATURESELECTORPOINT_DEBUG_
        qDebug() << " grow:" << grow << "ratio:" << ratio << "delta:" << QPointF(x, y) << "bottomLeft:" << r.bottomLeft();
#endif
    }
    else {
        r.setBottomLeft(mapToParent(ep));
    }
}

//========================================
//
// MvQLeftMidFeatureSelectorPoint
//
//========================================

MvQLeftMidFeatureSelectorPoint::MvQLeftMidFeatureSelectorPoint(MvQFeatureSelector* selector) :
    MvQFeatureSelectorPoint(selector, ":/uPlot/cursor_horiz.svg", ":/uPlot/cursor_horiz_hover.svg", -2, -1)
{
    updatePosition();
}

void MvQLeftMidFeatureSelectorPoint::updatePosition()
{
    QRectF r = selector_->boundingRect();
    setPos(r.x(), r.center().y());
}

void MvQLeftMidFeatureSelectorPoint::handleMouseMove(QPointF ep, QRectF& r)
{
    // ep: current mouse pos in item coordinates
    if (!selector_->keepAspectRatio()) {
        auto pp = mapToParent(ep);
        setPos(QPointF(pp.x(), r.center().y()));
        r.setLeft(pos().x());
    }
}

//========================================
//
// SelectorState
//
//========================================

class SelectorState
{
public:
    SelectorState()                               = default;
    virtual ~SelectorState()                      = default;
    virtual void setSelector(MvQFeatureSelector*) = 0;
    const QList<MvQFeatureItemPtr>& managedItems() const { return managedItems_; }
    virtual QList<MvQFeatureItemPtr> allItems() const { return managedItems_; }

    virtual void handleClear();
    virtual void handleCheckClick() {}
    virtual void handleDoubleClick() {}
    virtual void handleMousePress(QGraphicsSceneMouseEvent*) {}
    virtual void handleSelectionChanged(QList<MvQFeatureItemPtr>) {}
    virtual bool handleIdentifyCommandTarget(MvQFeatureCommandTarget*) { return false; }
    virtual void handleRemoveAll() {}
    virtual void handleItemStyleChanged(MvQFeatureItemPtr) {}
    virtual void handleSuspend(bool);

protected:
    MvQFeatureSelector* selector_{nullptr};
    QList<MvQFeatureItemPtr> managedItems_;
};

class SelectorSuspendState : public SelectorState
{
public:
    SelectorSuspendState() = default;
    void setSelector(MvQFeatureSelector*) override;
    void handleSuspend(bool) override;
};

class SelectorIdleState : public SelectorState
{
public:
    SelectorIdleState() = default;
    void setSelector(MvQFeatureSelector*) override;
    void handleSelectionChanged(QList<MvQFeatureItemPtr> items) override;
};

class SelectorNormalState : public SelectorState
{
public:
    SelectorNormalState(QList<MvQFeatureItemPtr>);
    void setSelector(MvQFeatureSelector*) override;
    void handleDoubleClick() override;
    void handleMousePress(QGraphicsSceneMouseEvent*) override;
    void handleSelectionChanged(QList<MvQFeatureItemPtr> items) override;
    bool handleIdentifyCommandTarget(MvQFeatureCommandTarget* s) override;
    //void handleRemoveAll() override;
    void handleItemStyleChanged(MvQFeatureItemPtr) override;
};

class SelectorPointEditState : public SelectorState
{
public:
    SelectorPointEditState(MvQFeatureItemPtr);
    void setSelector(MvQFeatureSelector*) override;
    QList<MvQFeatureItemPtr> allItems() const override { return {item_}; }

    void handleCheckClick() override;
    void handleSelectionChanged(QList<MvQFeatureItemPtr> items) override;
    bool handleIdentifyCommandTarget(MvQFeatureCommandTarget* s) override;
    void handleClear() override;

protected:
    MvQFeatureItemPtr item_;
};

class SelectorTextEditState : public SelectorState
{
public:
    SelectorTextEditState(MvQFeatureItemPtr);
    void setSelector(MvQFeatureSelector*) override;
    QList<MvQFeatureItemPtr> allItems() const override { return {item_}; }

    void handleSelectionChanged(QList<MvQFeatureItemPtr> items) override;
    bool handleIdentifyCommandTarget(MvQFeatureCommandTarget* s) override;

protected:
    MvQFeatureItemPtr item_{nullptr};
};

class SelectorLineState : public SelectorState
{
public:
    SelectorLineState(MvQFeatureItemPtr);
    void setSelector(MvQFeatureSelector*) override;
    QList<MvQFeatureItemPtr> allItems() const override { return {item_}; }

    void handleSelectionChanged(QList<MvQFeatureItemPtr> items) override;
    bool handleIdentifyCommandTarget(MvQFeatureCommandTarget* s) override;
    //void handleRemoveAll() override;
    void handleClear() override;

protected:
    MvQFeatureItemPtr item_{nullptr};
};

//========================================
//
// SelectorState
//
//========================================

void SelectorState::handleClear()
{
    selector_->transitionTo(new SelectorIdleState());
}

void SelectorState::handleSuspend(bool st)
{
    if (st) {
        selector_->transitionTo(new SelectorSuspendState());
    }
}

//========================================
//
// SelectorSuspendState
//
//========================================

void SelectorSuspendState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->hide();
}

void SelectorSuspendState::handleSuspend(bool st)
{
    if (!st) {
        selector_->transitionTo(new SelectorIdleState());
    }
}

//========================================
//
// SelectorIdleState
//
//========================================

void SelectorIdleState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->hide();
    selector_->controlPointUsed_ = false;
    selector_->controlPointMove_ = false;
    selector_->dragPos_          = QPointF();
}

void SelectorIdleState::handleSelectionChanged(QList<MvQFeatureItemPtr> items)
{
    if (items.size() == 0) {
        selector_->hide();
    }
    else if (items.size() == 1 && items[0]->isLine()) {
        selector_->transitionTo(new SelectorLineState(items[0]));
    }
    else {
        selector_->transitionTo(new SelectorNormalState(items));
    }
}

//========================================
//
// SelectorNormalState
//
//========================================

SelectorNormalState::SelectorNormalState(QList<MvQFeatureItemPtr> items)
{
    managedItems_ = items;
}

void SelectorNormalState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->managedItemsChanged();
}

void SelectorNormalState::handleDoubleClick()
{
    if (managedItems_.size() == 1) {
        auto item = managedItems_[0];
        if (item->hasPoints()) {
            selector_->transitionTo(new SelectorPointEditState(item));
        }
        else if (item->hasEditableText()) {
            selector_->transitionTo(new SelectorTextEditState(item));
        }
    }
}

void SelectorNormalState::handleMousePress(QGraphicsSceneMouseEvent* event)
{
    // the selector item is active, so it handles the mousePress.
    if (managedItems_.size() == 1) {
        auto item = managedItems_[0];
        // we see if this click concludes a double-click on the item. In this case
        // we switch into edit mode!
        if (item->elapsedSinceLastPressed() < qApp->doubleClickInterval() + 50) {
            handleDoubleClick();
            return;
        }
    }

    selector_->registerMousePress(event);
}

void SelectorNormalState::handleSelectionChanged(QList<MvQFeatureItemPtr> items)
{
    if (items.size() == 0) {
        selector_->transitionTo(new SelectorIdleState());
    }
    else if (items.size() == 1 && items[0]->isLine()) {
        selector_->transitionTo(new SelectorLineState(items[0]));
    }
    else {
        managedItems_ = items;
        selector_->managedItemsChanged();
    }
}

bool SelectorNormalState::handleIdentifyCommandTarget(MvQFeatureCommandTarget* s)
{
    QPointF itemPos = selector_->mapFromScene(s->scenePos());
    if (selector_->boundingRect().contains(itemPos)) {
        s->setInSelector(true);
        // the sole item will recieve the context event
        if (managedItems_.size() == 1) {
            s->setTargetItem(managedItems_[0]);
            return true;
            // the whole selector will receive the context event
        }
        else if (managedItems_.size() > 1) {
            s->setTargetItem(nullptr);
            return true;
        }
    }
    return false;
}

#if 0
void SelectorNormalState::handleRemoveAll()
{
    auto itemsCopy = managedItems_;
    managedItems_.clear();
    selector_->view_->removeFeatures(itemsCopy);
}
#endif

void SelectorNormalState::handleItemStyleChanged(MvQFeatureItemPtr item)
{
    if (managedItems_.contains(item)) {
        selector_->adjust(selector_->computeBRect());
    }
}

//========================================
//
// SelectorPointEditState
//
//========================================

SelectorPointEditState::SelectorPointEditState(MvQFeatureItemPtr item) :
    item_(item)
{
    Q_ASSERT(item_);
}

void SelectorPointEditState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->hide();
    //item->setSelected(false);
    item_->enterPointEdit();
}

void SelectorPointEditState::handleCheckClick()
{
    if (item_->currentAction_ == MvQFeatureItem::PointEditAction &&
        !item_->hasEditedPoint()) {
        item_->leavePointEdit();
    }
}

void SelectorPointEditState::handleSelectionChanged(QList<MvQFeatureItemPtr> items)
{
    item_->leavePointEdit();
    if (items.size() != 0) {
        selector_->transitionTo(new SelectorNormalState(items));
    }
    else {
        selector_->transitionTo(new SelectorIdleState());
    }
}

bool SelectorPointEditState::handleIdentifyCommandTarget(MvQFeatureCommandTarget* s)
{
    QPointF itemPos = item_->mapFromScene(s->scenePos());
    int nodeIndex   = item_->nodeForContextMenu(itemPos);
    if (nodeIndex != -1) {
        s->setInSelector(true);
        s->setTargetNodeItem(item_, nodeIndex);
        s->addDisabledCommands(item_->disabledCommands(nodeIndex));
        return true;
    }
    return false;
}

void SelectorPointEditState::handleClear()
{
    item_->leavePointEdit();
    SelectorState::handleClear();
}

//========================================
//
// SelectorTextEditState
//
//========================================

SelectorTextEditState::SelectorTextEditState(MvQFeatureItemPtr item) :
    item_(item)
{
    Q_ASSERT(item_);
}

void SelectorTextEditState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->hide();
    item_->editText();
}

void SelectorTextEditState::handleSelectionChanged(QList<MvQFeatureItemPtr> items)
{
    if (items.size() >= 1) {
        selector_->transitionTo(new SelectorNormalState(items));
    }
    else {
        selector_->transitionTo(new SelectorIdleState());
    }
}

bool SelectorTextEditState::handleIdentifyCommandTarget(MvQFeatureCommandTarget*)
{
    return false;
}

//========================================
//
// SelectorLineState
//
//========================================

SelectorLineState::SelectorLineState(MvQFeatureItemPtr item) :
    item_(item)
{
    Q_ASSERT(item_);
}

void SelectorLineState::setSelector(MvQFeatureSelector* selector)
{
    selector_ = selector;
    selector_->hide();
    //item->setSelected(false);
    item_->enterPointEdit();
}

void SelectorLineState::handleSelectionChanged(QList<MvQFeatureItemPtr> items)
{
    if (items.size() == 1 && items[0] == item_) {
        return;
    }
    else {
        if (item_) {
            item_->leavePointEdit();
        }
        if (items.size() != 0) {
            selector_->transitionTo(new SelectorNormalState(items));
        }
        else {
            selector_->transitionTo(new SelectorIdleState());
        }
    }
}

bool SelectorLineState::handleIdentifyCommandTarget(MvQFeatureCommandTarget* s)
{
    QPointF itemPos = item_->mapFromScene(s->scenePos());
    if (item_->boundingRect().contains(itemPos)) {
        s->setInSelector(true);
        s->setTargetItem(item_);
        return true;
    }
    return false;
}

#if 0
void SelectorLineState::handleRemoveAll()
{
    QList<MvQFeatureItemPtr> lst;
    lst << item_;
    selector_->view()->removeFeatures(lst);
}
#endif

void SelectorLineState::handleClear()
{
    item_->leavePointEdit();
    SelectorState::handleClear();
}

//========================================
//
// MvQFeatureSelector
//
//========================================

MvQFeatureSelector::MvQFeatureSelector(MvQPlotView* view, MvQFeatureHandler* handler, QGraphicsItem* parent) :
    view_(view),
    handler_(handler)
{
    pen_ = QPen(Qt::DashLine);
    pen_.setColor(Qt::black);
    brush_ = QBrush(QColor(0, 0, 255, 100));

    setAcceptDrops(true);

    // it stays on top of all the feature items!
    setZValue(2);
    setParentItem(parent);
    createControlPoints();

    transitionTo(new SelectorIdleState());
    Q_ASSERT(state_);

    hide();
}

QRectF MvQFeatureSelector::boundingRect() const
{
    return bRect_;
}

void MvQFeatureSelector::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*)
{
    painter->setPen(pen_);
    if (managedItems().size() == 1) {
        painter->drawRect(bRect_);
#ifdef MVQ_FEATURE_DEV_MODE_
        painter->setPen(Qt::red);
        //painter->drawRect(mapRectFromParent(computeBRect()));
#endif
    }
    else {
        for (auto item : managedItems()) {
            auto br = item->mapToParent(item->boundingRect()).boundingRect();
            br      = mapFromParent(br).boundingRect();
            painter->drawRect(br);
        }
    }
}

const QList<MvQFeatureItemPtr>& MvQFeatureSelector::managedItems() const
{
    return state_->managedItems();
}

QList<MvQFeatureItemPtr> MvQFeatureSelector::allItems() const
{
    return state_->allItems();
}

MvQFeatureItemPtr MvQFeatureSelector::firstItem() const
{
    auto lst = allItems();
    return (lst.size() > 0) ? lst[0] : nullptr;
}

void MvQFeatureSelector::adjust(QRectF r)
{
    prepareGeometryChange();
    bRect_ = QRectF(0, 0, r.width(), r.height());
    setPos(r.topLeft());
    for (auto h : controlPoints_) {
        h->updatePosition();
    }
    update();
}

void MvQFeatureSelector::createControlPoints()
{
    if (controlPoints_.isEmpty()) {
        controlPoints_ << new MvQTopLeftFeatureSelectorPoint(this);
        controlPoints_ << new MvQTopMidFeatureSelectorPoint(this);
        controlPoints_ << new MvQTopRightFeatureSelectorPoint(this);
        controlPoints_ << new MvQRightMidFeatureSelectorPoint(this);
        controlPoints_ << new MvQBottomRightFeatureSelectorPoint(this);
        controlPoints_ << new MvQBottomMidFeatureSelectorPoint(this);
        controlPoints_ << new MvQBottomLeftFeatureSelectorPoint(this);
        controlPoints_ << new MvQLeftMidFeatureSelectorPoint(this);
    }
}

void MvQFeatureSelector::controlPointPressed(MvQFeatureSelectorPoint*)
{
    oriRect_          = bRect_;
    controlPointUsed_ = true;
    controlPointMove_ = false;
}

//Here rect is in item coordinates
void MvQFeatureSelector::controlPointMoved(MvQFeatureSelectorPoint*, QRectF rect)
{
    if (!controlPointUsed_)
        return;

#ifdef MVQFEATURESELECTORITEM_DEBUG_
    qDebug() << "MvQFeatureSelectorItem::controlPointMoved";
#endif

    if (rect.isValid()) {
        QRectF oriRect = mapRectToParent(bRect_);
        auto xSign     = (rect.left() - rect.right()) * (oriRect.left() - oriRect.right());
        auto ySign     = (rect.top() - rect.bottom()) * (oriRect.top() - oriRect.bottom());
        auto mSize     = minSize();
#ifdef MVQFEATURESELECTORITEM_DEBUG_
        qDebug() << "xSign:" << xSign << "ySign:" << ySign << "mSize:" << mSize << "rectSize:" << rect.size();
#endif
        // we cannot shrink the selector below a certain size, no flip allowed
        if (fabs(rect.width()) >= mSize.width() && fabs(rect.height()) >= mSize.height() &&
            xSign >= 0. && ySign >= 0.) {
            prepareGeometryChange();
#ifdef MVQFEATURESELECTORITEM_DEBUG_
            qDebug() << " bRect_(parent):" << oriRect << "rect:" << rect;
#endif
            QPointF dp = rect.topLeft() - bRect_.topLeft();
            bRect_     = QRectF(0, 0, rect.width(), rect.height());
            setPos(pos() + dp);
#ifdef MVQFEATURESELECTORITEM_DEBUG_
            qDebug() << "dp:" << dp << "bRect:" << bRect_;
#endif
            QRectF newRect = mapToParent(bRect_).boundingRect();
#ifdef MVQFEATURESELECTORITEM_DEBUG_
            qDebug() << " --> bRect_ in parent" << newRect;
#endif
            for (MvQFeatureSelectorPoint* h : controlPoints_) {
                h->updatePosition();
            }

            handler_->resize(managedItems(), newRect, oriRect, !controlPointMove_);
            controlPointMove_ = true;
            return;
        }
    }

    for (MvQFeatureSelectorPoint* h : controlPoints_) {
        h->updatePosition();
    }
}

void MvQFeatureSelector::controlPointReleased(MvQFeatureSelectorPoint*)
{
    controlPointUsed_ = false;
    controlPointMove_ = false;
    for (MvQFeatureItemPtr item : managedItems()) {
        item->resizeFinished();
    }
}

void MvQFeatureSelector::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* /*event*/)
{
    if (controlPointUsed_)
        return;
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    qDebug() << "MvQFeatureSelectorItem::mouseDoubleClickEvent";
#endif
    state_->handleDoubleClick();
}

void MvQFeatureSelector::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    qDebug() << "MvQFeatureSelectorItem::mousePressEvent size=" << managedItems().size();
#endif
    if (controlPointUsed_)
        return;

    state_->handleMousePress(event);
}

void MvQFeatureSelector::registerMousePress(QGraphicsSceneMouseEvent* event)
{
    dragPos_            = mapToParent(event->pos());
    currentMouseAction_ = MousePressedAction;
}

void MvQFeatureSelector::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    qDebug() << "MvQFeatureSelectorItem::mouseMoveEvent event.pos" << mapToParent(event->pos()) << "dragPos:" << dragPos_ << "pos:" << pos() << "items.size=" << managedItems().size();
    qDebug() << "  event.lastPos" << mapToParent(event->lastPos());
#endif

    if (controlPointUsed_) {
        return;
    }

    QPointF prevPos = pos();
    auto evtPos     = mapToParent(event->pos());
    auto delta      = evtPos - dragPos_;

    // we came here without having a mouse press event on the selector
    if (currentMouseAction_ == NoMouseAction) {
        delta = QPointF();
        handler_->moveBy(nonGeoLockedManagedItems(), delta, true);
    }
    else {
        MvQFeatureItemList nonGlItems = nonGeoLockedManagedItems();
        if (nonGlItems.size() == 0) {
            return;
        }
        //        if (std::all_of(items.cbegin(), items.cend(), [](auto v) {return v->isGeoLocked();})) {
        //            return;
        //        }

        QPointF pp = pos() + delta;
        setPos(pp);
        update();
#ifdef MVQFEATURESELECTORITEM_DEBUG_
        qDebug() << " delta=" << delta;
#endif
        delta = pos() - prevPos;
        //        handler_->moveBy(managedItems(), delta, currentMouseAction_ != MouseMoveAction);
        handler_->moveBy(nonGlItems, delta, currentMouseAction_ != MouseMoveAction);
    }

    dragPos_ += delta;

    currentMouseAction_ = MouseMoveAction;
}


void MvQFeatureSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent*)
{
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    //qDebug() << "MvQFeatureSelectorItem::mouseReleasEvent";
#endif
    if (controlPointUsed_)
        return;

    ungrabMouse();
    currentMouseAction_ = NoMouseAction;
}

void MvQFeatureSelector::suspend(bool st)
{
    state_->handleSuspend(st);
}

// Cannot be called from a state since it will delete the current state!
void MvQFeatureSelector::clear()
{
    auto allIt = allItems();
    state_->handleClear();
    suspend(true);
    for (auto it : allIt) {
        it->setSelected(false);
    }
    suspend(false);
}

#if 0
void  MvQFeatureSelector::removeAllItems()
{
    state_->handleRemoveAll();
}
#endif

// called when the selection changed in the view
void MvQFeatureSelector::selectionChanged()
{
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    qDebug() << "MvQFeatureSelectorItem::selectionChanged";
#endif
    state_->handleSelectionChanged(currentSelection());
}

QList<MvQFeatureItemPtr> MvQFeatureSelector::currentSelection() const
{
    Q_ASSERT(parentItem());
    QList<MvQFeatureItemPtr> it;
    for (auto item : parentItem()->childItems()) {
        if (item->isSelected() && item->type() == MvQ::FeatureItemType) {
            auto* f = static_cast<MvQFeatureItem*>(item);
            Q_ASSERT(f);
            if (!f->isPoint()) {
                //if (f->currentAction_ != MvQFeatureItem::PointEditAction) {
#ifdef MVQFEATURESELECTORITEM_DEBUG_
                qDebug() << "   selected:" << f->typeName() << f;
#endif
                it << f->getptr();
            }
        }
    }
    return it;
}

void MvQFeatureSelector::managedItemsChanged()
{
    auto items = state_->managedItems();

    if (items.count() == 0) {
        hide();
    }
    else {
        // construct the bounding rect
        auto br = computeBRect();
        adjust(br);

        for (auto h : controlPoints_) {
            h->show();
        }
        // hide mid control points when aspect ratio is kept
        if (keepAspectRatio()) {
            Q_ASSERT(controlPoints_.count() > 7);
            controlPoints_[1]->hide();
            controlPoints_[3]->hide();
            controlPoints_[5]->hide();
            controlPoints_[7]->hide();
        }

        show();

        // we need it if we immediately drag the selected item. In this case first the item receives a
        // a mousePress event, then the current function is called followed by a mouseMove event
        // on the selector. dragPos is in parent coordinates!
        dragPos_ = mapToParent(mapFromItem(items[0].get(), items[0]->lastPressedPos_));

#ifdef MVQFEATURESELECTORITEM_DEBUG_
        qDebug() << "   pos:" << pos() << " dragPos:" << dragPos_;
#endif
        setSelected(true);
        grabMouse();
    }
}

void MvQFeatureSelector::itemStyleChanged(MvQFeatureItemPtr item)
{
    state_->handleItemStyleChanged(item);
}

void MvQFeatureSelector::adjustToItems()
{
    adjust(computeBRect());
}

QRectF MvQFeatureSelector::computeBRect() const
{
    QRectF br;
    if (managedItems().size() > 0) {
        auto item = managedItems()[0];
        QRectF p  = item->boundingRect();
        br        = item->mapToParent(p).boundingRect();
        for (int i = 1; i < managedItems().count(); i++) {
            item = managedItems()[i];
            p    = item->boundingRect();
            br   = br.united(item->mapToParent(p).boundingRect());
        }
    }
    return br;
}

QSizeF MvQFeatureSelector::minSize() const
{
    double minSizeX = 8.;
    double minSizeY = 8.;
    for (auto it : managedItems()) {
        auto m = it->minSize();
        if (m.width() > minSizeX) {
            minSizeX = m.width();
        }
        if (m.height() > minSizeY) {
            minSizeY = m.height();
        }
    }
    return QSizeF(minSizeX, minSizeY);
}

bool MvQFeatureSelector::identifyCommandTarget(MvQFeatureCommandTarget* s)
{
    return state_->handleIdentifyCommandTarget(s);
}

bool MvQFeatureSelector::keepAspectRatio() const
{
    if (managedItems().count() > 0) {
        return managedItems()[0]->keepAspectRatio();
    }
    return false;
}

MvQFeatureItemList MvQFeatureSelector::nonGeoLockedManagedItems() const
{
    const auto& items = managedItems();
    MvQFeatureItemList nonGlItems;
    for (auto it : items) {
        if (!it->isGeoLocked()) {
            nonGlItems << it;
        }
    }
    return nonGlItems;
}

void MvQFeatureSelector::transitionTo(SelectorState* state)
{
#ifdef MVQFEATURESELECTORITEM_DEBUG_
    std::cout << " MvQFeatureSelectorItem::transitionTo " << typeid(*state).name() << "\n";
#endif
    if (state_ != nullptr) {
        delete state_;
    }
    state_ = state;
    state_->setSelector(this);
}
