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

#pragma once

#include "MvRequest.h"

#include <QElapsedTimer>
#include <QGraphicsItem>
#include <QJsonObject>
#include <QMimeData>
#include <QPixmap>
#include <QPen>
#include <QStringList>
#include <QTextStream>
#include <QUndoCommand>

#include "MvQFeatureFwd.h"

class QMouseEvent;
class MgQLayoutItem;
class MvQFeatureCommandTarget;
class WsType;
class MvQFeatureItem;
class MvQFeatureSelector;
class MvQPlotView;

namespace MvQ
{
const int FeatureItemType = QGraphicsItem::UserType + 1;
}

class MvQFeatureItemObserver;

class MvQFeatureGeometry
{
public:
    QRectF bRect_;
    QRectF itemRect_;
    QRectF textRect_;
    QPointF pos_;
    QPointF geoCoord_{QPointF(0., 0.)};
    QVector<QPointF> pointsScenePos_;
    QVector<QPointF> pointsGeoCoord_;
};

class MvQFeatureDescriber
{
public:
    MvQFeatureDescriber();
    QString text() const { return txt_; }
    void addTitle(QString val);
    void add(QString label, void* p);
    void add(QString label, QVariant val);

private:
    QString txt_;
    QTextStream s_;
    static QString indent_;
};

class MvQFeatureItem : public QGraphicsItem, public std::enable_shared_from_this<MvQFeatureItem>
{
    friend class SelectorState;
    friend class SelectorPointEditState;
    friend class MvQFeatureSelector;
    friend class MvQFeatureHandler;
    friend class MvQFeatureToFrontCommand;
    friend class MvQFeatureToForwardCommand;
    friend class MvQFeatureToBackCommand;
    friend class MvQFeatureToBackwardCommand;
    friend class MvQFeatureFlipCommand;
    friend class MvQFeatureAddNodeCommand;
    friend class MvQFeatureRemoveNodeCommand;
    friend class MvQFeatureMoveNodeCommand;

public:
    enum CurrentAction
    {
        NoAction,
        MoveAction,
        ResizeAction,
        CreationAction,
        InsertAction,
        PointEditEnterAction,
        PointEditAction
    };
    enum AnchorPosition
    {
        CentreAnchor,
        BottomCentreAnchor
    };
    enum
    {
        Type = MvQ::FeatureItemType
    };

    // Only shared pointers should be created to an MvQFeatureItem. This must be done
    // via the MvQFeatureFactory!
    // The problem is that the qt scene and view only use raw pointers. For that reason the
    // item management is restricted to the MvQFeatureHandler singleton, and we should never use
    // raw pointers to items outside it.

    // One exception is the control points of curve objects. They are only used internally in the
    // parent curve so for simplicity we only create and use raw pointers to them.

    ~MvQFeatureItem() override;
    MvQFeatureItem(const MvQFeatureItem&) = delete;
    MvQFeatureItem& operator=(const MvQFeatureItem&) = delete;

    std::shared_ptr<MvQFeatureItem> getptr() { return shared_from_this(); }

    virtual void init(const QJsonObject& d);
    virtual void toJson(QJsonObject&);

    QRectF boundingRect() const override { return bRect_; }
    int type() const override { return Type; }
    QString typeName() const;
    QString typeDescription() const;
    QRectF itemRect() const { return itemRect_; }
    QPointF scenePos() const;
    QPointF copiedScenePos() const { return copiedScenePos_; }

    MvQPlotView* plotView() const { return plotView_; }
    WsType* feature() const { return feature_; }
    MgQLayoutItem* dataLayout() const { return dataLayout_; }
    bool highlighted() const { return highlighted_; }
    virtual bool canBeCreatedOutOfView() const { return true; }
    virtual bool canBePastedOutOfView() const { return true; }
    const std::string& sceneId() const { return sceneId_; }

    CurrentAction currentAction() const { return currentAction_; }
    const MvRequest& styleRequest() const { return req_; }
    virtual void setStyle(const MvRequest& req = {}, bool init = false);

    virtual QPointF geoCoord() const { return geoCoord_; }
    virtual QList<QPointF> geoCoords() const { return {geoCoord_}; }
    void initScenePosition(MgQLayoutItem*, const QPointF&);
    virtual void prepareForProjectionChange() {}
    virtual void projectionChanged(MgQLayoutItem*, bool init);
    virtual void checkLayoutOnMove();
    void initFromStoredGeoCoord(MgQLayoutItem* dataLayout);

    void setHighlighted(bool);

    virtual void moveBy(QPointF);
    virtual void moveTo(const MvQFeatureGeometry&);
    virtual void resize(QRectF rect) = 0;
    virtual void resizeTo(const MvQFeatureGeometry&) = 0;
    void adjustSizeToSelector(QRectF rectNew, QRectF rectOri);
    virtual void resizeFinished() {}

    virtual QString text() const { return {}; }
    virtual void setText(QString) {}

    virtual QStringList disabledCommands(int /*nodeIdx=-1*/) const { return {}; }
    bool identifyCommandTarget(MvQFeatureCommandTarget* s);
    void styleEdited(MvRequest& req);
    void styleEditedInRibbon(MvRequest& req, StyleEditInfo info = StyleEditInfo::StandardStyleEdit);
    bool keepAspectRatio() const { return keepAspectRatio_; }
    virtual void initContents() {}
    virtual QSizeF minSize() const;
    qint64 elapsedSinceLastPressed() const { return lastPressed_.elapsed(); }

    virtual bool isPoint() const { return false; }
    virtual bool hasPoints() const { return false; }
    virtual bool hasEditableText() const { return false; }
    virtual bool isLine() const { return false; }
    virtual void enterPointEdit() {}
    virtual void startPointEdit() {}
    virtual void leavePointEdit() {}
    virtual bool hasEditedPoint() { return false; }
    virtual void editText() {}

    static float bottomZValue() { return bottomZValue_; }
    static float topZValue() { return topZValue_; }
    static float zValueIncrement() { return zValueIncrement_; }
    static float nextZValue() { return nextZValue_; }

    bool isGeoLocked() const { return geoLocked_; }

    virtual const std::string& frontSubType() const
    {
        static std::string emptyStr;
        return emptyStr;
    }
    virtual void setFrontSubType(const std::string&) {}

    virtual MvQFeatureGeometry getGeometry() const;
    virtual QString describe() const;

    void addObserver(MvQFeatureItemObserver*);
    void removeObserver(MvQFeatureItemObserver*);

    QPixmap toPixmap();

protected:
    MvQFeatureItem(WsType*);
    MvQFeatureItem(QString featureTypeName, QGraphicsItem* parent);

    void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;
    void hoverEnterEvent(QGraphicsSceneHoverEvent*) override;
    void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override;
    QVariant itemChange(GraphicsItemChange change, const QVariant& value) override;
    QRectF rectToSelector(QRectF rectNew, QRectF rectOri);

    void adjustGeoCoord();
    QPointF computeGeoCoord();
    QPointF computeGeoCoord(QPointF scPos);
    static bool isValidPoint(QPointF p);
    virtual void initAtGeoCoord(QPointF);
    virtual void initAtScenePos(const QPointF&);
    virtual void reproject(bool init);
    void inspectProjection();

    virtual void makePixmap() {}
    virtual void updateSymbols() {}
    virtual void adjustBRect() {}
    QString reqVerb() const;
    virtual void initGeometry() {}
    void initStyle();
    void editStyle();
    void expandStyleRequest();
    virtual double halo() const { return 0.; }

    virtual void moveNodeTo(int /*nodeIndex*/, const QPointF& /*scenePos*/) {}
    virtual void addNode(int /*node*/, bool) {}
    virtual void addNode(int /*node*/, const QPointF&) {}
    virtual void removeNode(int /*nodeIdx*/) {}
    virtual int nodeForContextMenu(const QPointF&) { return -1; }

    void adjustToolTip();
    void adjustGeoLocked();

    virtual double realZValue() const;
    virtual void setRealZValue(double z);

    virtual void flipSymbolDirection() {}
    virtual bool getRequestParameters() { return false; }
    void setClipping(QPainter* painter);

    void broadcastRequestChanged();

    WsType* feature_{nullptr};
    MvQPlotView* plotView_{nullptr};
    MvQFeatureSelector* selector_{nullptr};
    CurrentAction currentAction_{NoAction};
    MgQLayoutItem* dataLayout_{nullptr};
    QRectF bRect_;
    QRectF itemRect_;
    QPointF dragPos_;
    QPointF copiedScenePos_;
    bool outOfView_{true};
    bool highlighted_{false};
    bool keepAspectRatio_{false};
    AnchorPosition anchorPos_{CentreAnchor};
    QPointF geoCoord_{QPointF(0., 0.)};
    bool geoLocked_{false};
    bool hasValidGeoCoord_{false};
    bool projPeriodic_{false};
    std::string sceneId_;
    MvRequest req_;
    static const float bottomZValue_;
    static const float topZValue_;
    static const float zValueIncrement_;
    static float nextZValue_;
    QString typeName_;
    QElapsedTimer lastPressed_;
    QPointF lastPressedPos_;
    std::vector<MvQFeatureItemObserver*> observers_;
    static QColor devModeColor_;

private:
    void initInner();
};
