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

#pragma once

#include <QGraphicsItem>
#include <QIcon>
#include <QPixmap>
#include <QPen>
#include <QVector>

#include "MvQFeatureItem.h"
#include "MvRequest.h"
#include "Spline.h"

class QJsonObject;
class MvQFeatureCurveBaseItem;

class MvQFeatureLineEdgeShape
{
public:
    MvQFeatureLineEdgeShape() = default;
    void paint(QPainter* painter, const QPen& pen);
    bool update(const std::string& type, float angle, float len);
    void build(const QPointF& p1, const QPointF& p2);
    QRectF bRect(int penWidth) const;
    void adjustLineSize(QPolygonF&, bool) const;
    void clearShape();

private:
    template <typename Iterator>
    int adjustLineSizeFA(Iterator, Iterator) const;

    std::string type_{"LINE"};
    float angle_{25.};
    float length_{10.};
    QPolygonF shape_;

    // Intersection point of two lines: line joining the arrow's ends and
    // the orthogonal line
    QPointF crossPoint_{QPointF(0., 0.)};
};

//-----------------------------------------------------------
// The points of a polyline/curve object
//-----------------------------------------------------------
class MvQFeatureCurvePointItem : public MvQFeatureItem
{
    friend class MvQFeatureCurveBaseItem;
    friend class MvQFeatureGeoPolylineItem;
    friend class MvQFeatureGeoQuadItem;
    friend class MvQFeatureMoveNodeCommand;

public:
    MvQFeatureCurvePointItem(MvQFeatureCurveBaseItem* owner);

    void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
    void resize(QRectF /*rect*/) override {}
    void resizeTo(const MvQFeatureGeometry&) override {}
    void enterCreateMode();
    void enterInsertMode();
    bool containsInHalo(QPointF pp);
    bool isPoint() const override { return true; }
    void resetHover();
    void projectionChanged(MgQLayoutItem*, bool) override {}
    QString describe() const override;

private:
    void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) override;
    void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
    QVariant itemChange(GraphicsItemChange change, const QVariant& value) override;
    void reproject(bool init) override;

    MvQFeatureCurveBaseItem* curve_;
    QBrush brush_{Qt::white};
    QPen pen_{Qt::black};
    QBrush selBrush_{Qt::green};
    QPen selPen_{Qt::black};
    bool hover_{false};
    bool inMove_{false};
};

//-----------------------------------------------------------------
// The base class for polyline/curve items
// (= collection of points with line/curve between them)
//-----------------------------------------------------------------
class MvQFeatureCurveBaseItem : public MvQFeatureItem
{
    friend class MvQFeatureCurvePointItem;

public:
    void init(const QJsonObject& d) override;
    //    void toJson(QJsonObject&) override;

    void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override;
    void resize(QRectF rect) override;
    void resizeTo(const MvQFeatureGeometry& geom) override;
    void reproject(bool init) override;
    void moveBy(QPointF) override;
    void moveTo(const MvQFeatureGeometry& g) override;
    void createNext();
    void createStop();
    bool hasPoints() const override { return true; }
    int nodeIndex(const MvQFeatureCurvePointItem* p) const;
    void adjustHover(MvQFeatureCurvePointItem* p);
    bool hasHoveredPoints() const;
    QPainterPath shape() const override { return bShape_; }
    QStringList disabledCommands(int nodeIdx = -1) const override;
    MvQFeatureGeometry getGeometry() const override;
    QString describe() const override;
    QList<QPointF> geoCoords() const override;

    bool canBeCreatedOutOfView() const override { return false; }
    bool canBePastedOutOfView() const override { return false; }

protected:
    MvQFeatureCurveBaseItem(WsType*, bool closed);
    virtual void createStart();
    QRectF pointBoundingBox() const;
    virtual void updateRect();
    void adjustBRect() override;
    int nodeForContextMenu(const QPointF&) override;
    void addNode(int node, bool) override;
    void addNode(int node, const QPointF&) override;
    void removeNode(int nodeIdx) override;
    virtual void pointMoved(MvQFeatureCurvePointItem* p);
    void moveNodeTo(int nodeIndex, const QPointF& scenePos) override;
    void enterPointEdit() override;
    void startPointEdit() override;
    void leavePointEdit() override;
    bool hasEditedPoint() override;
    virtual void buildCurve() = 0;
    QRectF curveBoundingBox();
    void parallelCurves(const std::vector<double>& px, const std::vector<double>& py, QVector<QPointF>& pLeft, QVector<QPointF>& pRight, float distLeft, float distRight) const;
    void buildShapeCore(const std::vector<double>& px, const std::vector<double>& py, float distLeft, float distRight);
    virtual void buildShape();
    void generateCurvePath();
    virtual QPointF extrapolatePoint(bool before) const = 0;
    virtual QPointF midPoint(int idx1, int idx2) const  = 0;
    virtual void adjustPointsInResize(QList<QPointF> delta);
    virtual void adjustSelectorIfNeeded() const {}
    bool getRequestParameters() override;
    double halo() const override { return 8; }
    bool isFilled() const;
    virtual void prepareForPointRemove(int /*idx*/) {}
    double realZValue() const override;
    void setRealZValue(double z) override;
    void initAtGeoCoord(QPointF) override;
    void initAtScenePos(const QPointF&) override;
    void buildArrow();

    // the MvQFeatureCurvePointItem pointers must not be used ouside this class!!!
    QVector<MvQFeatureCurvePointItem*> points_;
    bool closed_{false};
    int maxPoints_{-1};

    float symbolWidth_;
    QSizeF symbolSize_;
    QVector<QPointF> symb_;

    QPen linePen_;
    QBrush fillBrush_;
    QBrush symbolBrush_;
    QPainterPath bShape_;
    QPointF bRectMargin_;

    std::vector<double> curveX_;
    std::vector<double> curveY_;
    QPainterPath curvePath_;
    QRectF curveRect_;
    double realZValue_{0};

    // Handle arrow point shape
    MvQFeatureLineEdgeShape startPointShape_;
    MvQFeatureLineEdgeShape endPointShape_;
};

//----------------------------------------------------------------------
// Base class implementing a polyline (open or closed)
//----------------------------------------------------------------------
class MvQFeaturePolylineItem : public MvQFeatureCurveBaseItem
{
public:
    double halo() const override;
    QSizeF minSize() const override;

protected:
    using MvQFeatureCurveBaseItem::MvQFeatureCurveBaseItem;
    void buildCurve() override;
    QPointF extrapolatePoint(bool before) const override;
    QPointF midPoint(int idx1, int idx2) const override;
    QPointF computeBRectMargin(const QRectF& itemRect) const;
};

//----------------------------------------------------------------------
// Polyline
//----------------------------------------------------------------------
class MvQFeaturePolylineLineItem : public MvQFeaturePolylineItem
{
    friend class MvQFeatureMaker<MvQFeaturePolylineLineItem>;

protected:
    MvQFeaturePolylineLineItem(WsType* feature);
};

class MvQFeatureLineItem : public MvQFeaturePolylineLineItem
{
    friend class MvQFeatureMaker<MvQFeatureLineItem>;

public:
    bool isLine() const override { return true; }

protected:
    MvQFeatureLineItem(WsType* feature);
};

//----------------------------------------------------------------------
// Polygon
//----------------------------------------------------------------------
class MvQFeatureClosedPolylineItem : public MvQFeaturePolylineItem
{
    friend class MvQFeatureMaker<MvQFeatureClosedPolylineItem>;

protected:
    MvQFeatureClosedPolylineItem(WsType* feature);
};

//----------------------------------------------------------------------
// Base class implementing a geopolyline (open or closed)
//----------------------------------------------------------------------
class MvQFeatureGeoPolylineItem : public MvQFeaturePolylineItem
{
public:
    void init(const QJsonObject& d) override;
    void toJson(QJsonObject&) override;
    void reproject(bool init) override;
    void moveBy(QPointF) override;
    void moveTo(const MvQFeatureGeometry& g) override;

protected:
    using MvQFeaturePolylineItem::MvQFeaturePolylineItem;
    void pointMoved(MvQFeatureCurvePointItem* p) override;
    void adjustPointsInResize(QList<QPointF> delta) override;
    virtual void adjustGeoShapeToMove(QPointF delta, QList<QPointF>& gc);
    virtual void adjustGeoShapeToDelta(QList<QPointF> delta, QList<QPointF>& gc);
    virtual bool adjustGeoShape(MvQFeatureCurvePointItem*, QList<QPointF>& gc);
    void buildCurve() override;
    void prepareForPointRemove(int idx) override;
    void checkDateLineCrossing(QPointF& c1, QPointF& c2, int idx);
    void checkGeoRange(QList<QPointF>& gc);
    void initAtGeoCoord(QPointF) override;

    //    QPointF extrapolatePoint(bool before) const override;
    //    QPointF midPoint(int idx1, int idx2) const override;
    //    QPointF computeBRectMargin(const QRectF& itemRect) const;

    double latlonIncr_{0.5};
    std::vector<double> segmentDx_;
    std::vector<bool> segmentAcrossDateLine_;
};

class MvQFeatureGeoPolylineLineItem : public MvQFeatureGeoPolylineItem
{
    friend class MvQFeatureMaker<MvQFeatureGeoPolylineLineItem>;

protected:
    MvQFeatureGeoPolylineLineItem(WsType* feature);
};

class MvQFeatureClosedGeoPolylineItem : public MvQFeatureGeoPolylineItem
{
    friend class MvQFeatureMaker<MvQFeatureClosedGeoPolylineItem>;

protected:
    MvQFeatureClosedGeoPolylineItem(WsType* feature);
};

class MvQFeatureGeoLineItem : public MvQFeatureGeoPolylineLineItem
{
    friend class MvQFeatureMaker<MvQFeatureGeoLineItem>;

public:
    bool isLine() const override { return true; }

protected:
    MvQFeatureGeoLineItem(WsType* feature);
};

class MvQFeatureGeoQuadItem : public MvQFeatureGeoPolylineItem
{
    friend class MvQFeatureMaker<MvQFeatureGeoQuadItem>;

protected:
    MvQFeatureGeoQuadItem(WsType* feature);
    void createStart() override;
    void adjustGeoShapeToDelta(QList<QPointF> delta, QList<QPointF>& gc) override;
    bool adjustGeoShape(MvQFeatureCurvePointItem*, QList<QPointF>& gc) override;
    void adjustSelectorIfNeeded() const override;
};


//----------------------------------------------------------------------
// Base class implementing a curve (aka spline) object (open or closed)
//----------------------------------------------------------------------
class MvQFeatureCurveItem : public MvQFeatureCurveBaseItem
{
public:
    double halo() const override;
    QSizeF minSize() const override;

protected:
    MvQFeatureCurveItem(WsType* feature, bool closed);
    static constexpr double MIN_SAMPLE_DS = 3;  // maximum spline segment lenght
    void buildCurve() override;
    QPointF extrapolatePoint(bool before) const override;
    QPointF midPoint(int idx1, int idx2) const override;

    SplineParam spline_;
    int splineNum_{100};
    double sampleDs_{MIN_SAMPLE_DS};
    float ds_{0.};  // line segment size
};

//----------------------------------------------------------------------
// Open curve
//----------------------------------------------------------------------
class MvQFeatureCurveLineItem : public MvQFeatureCurveItem
{
    friend class MvQFeatureMaker<MvQFeatureCurveLineItem>;

protected:
    MvQFeatureCurveLineItem(WsType* feature);
};

//----------------------------------------------------------------------
// Closed curve
//----------------------------------------------------------------------
class MvQFeatureClosedCurveItem : public MvQFeatureCurveItem
{
    friend class MvQFeatureMaker<MvQFeatureClosedCurveItem>;

protected:
    MvQFeatureClosedCurveItem(WsType* feature);
};
