/***************************************************************************
 *   Copyright (C) 2004-2005 by David Saxton                               *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#ifndef CANVAsmANIPULATOR_H
#define CANVAsmANIPULATOR_H

#include "eventinfo.h"

//#include <canvas.h> // 2018.10.16 - not needed
#include "canvasitems.h"
#include <QPointer>

class CanvasManipulator;
class Connector;
class CMManager;
class CNItem;
class CNItemGroup;
class EventInfo;
class FlowContainer;
class ICNDocument;
class Item;
class ItemDocument;
class ItemGroup;
class ItemView;
class ManipulatorInfo;
class ManualConnectorDraw;
class MechanicsItem;
class MechanicsGroup;
class MechanicsDocument;
class Node;
class NodeGroup;
class ResizeHandle;

class KtlQCanvas;
class KtlQCanvasItem;
class KtlQCanvasLine;
class KtlQCanvasRectangle;
class QMouseEvent;
class QTimer;
class QWheelEvent;

typedef CanvasManipulator *(*CreateManipulatorPtr)(ItemDocument *, CMManager *);
typedef bool (*AcceptManipulationPtr)(uint eventState, uint cmState, uint itemType, uint cnItemType);
typedef QList<NodeGroup *> NodeGroupList;
typedef QList<QPointer<Connector>> ConnectorList;
typedef QList<QPoint> QPointList;

class ManipulatorInfo
{
public:
    ManipulatorInfo();
    AcceptManipulationPtr m_acceptManipulationPtr;
    CreateManipulatorPtr m_createManipulatorPtr;
};
typedef QList<ManipulatorInfo *> ManipulatorInfoList;

/**
Handles canvas manipulation, such as moving an item or resizing the canvas
@author David Saxton
*/
class CMManager : public QObject
{
    Q_OBJECT
public:
    enum EventState { es_right_click = 1 << 0, es_ctrl_pressed = 1 << 1 };
    enum CMState { cms_repeated_add = 1 << 0, cms_manual_route = 1 << 1, cms_draw = 1 << 2 };
    enum ItemType { it_none = 1 << 0, it_node = 1 << 1, it_connector = 1 << 2, it_pin = 1 << 3, it_canvas_item = 1 << 4, it_mechanics_item = 1 << 5, it_resize_handle = 1 << 6, it_drawpart = 1 << 7 };

    enum ItemStateInfo { isi_isMovable = 0x2 };
    CMManager(ItemDocument *itemDocument);
    ~CMManager() override;
    /**
     * Called when the user single-clicks the mouse
     */
    void mousePressEvent(EventInfo eventInfo);
    /**
     * Called when the user releases the mouse
     */
    void mouseReleaseEvent(const EventInfo &eventInfo);
    /**
     * Called when the user double clicks the mouse
     */
    void mouseDoubleClickEvent(const EventInfo &eventInfo);
    /**
     * Called when the user moves the mouse
     */
    void mouseMoveEvent(const EventInfo &eventInfo);
    /**
     * Called when the user scrolls the mouse
     */
    void wheelEvent(const EventInfo &eventInfo);
    /**
     * Set a current CMState to true or false
     */
    void setCMState(CMState type, bool state);
    /**
     * Cancels the current manipulation (if there is one)
     */
    void cancelCurrentManipulation();
    CanvasManipulator *currentManipulator() const
    {
        return m_canvasManipulator;
    }
    void setRepeatedAddId(const QString &repeatedId = QString());
    uint cmState() const
    {
        return m_cmState;
    }
    void addManipulatorInfo(ManipulatorInfo *info);
    QString repeatedItemId() const
    {
        return m_repeatedItemId;
    }
    void setDrawAction(int drawAction);
    int drawAction() const
    {
        return m_drawAction;
    }

public slots:
    void slotSetManualRoute(bool manualRoute);

signals:
    void manualRoutingChanged(bool manualRouting);

protected:
    /**
     * Called when the mouse is moved or released, with the ResizeHandle that
     * the mouse is currently over (which can be null). Updates which handle is
     * selected, etc.
     */
    void updateCurrentResizeHandle(ResizeHandle *mouseOver);
    CanvasManipulator *m_canvasManipulator;
    uint m_cmState;
    QString m_repeatedItemId;
    ItemDocument *p_itemDocument;
    ManipulatorInfoList m_manipulatorInfoList;
    QPointer<Item> p_lastMouseOverItem; // Pointer to the item where the mouse was last over - this is used to determine when mouse
    QPointer<ResizeHandle> p_lastMouseOverResizeHandle;
    QPointer<Item> p_lastItemClicked;
    QTimer *m_allowItemScrollTmr; // When a user scrolls on the canvas, we don't want to stop scrolling when the user gets to (e.g.) a scrollable widget. So this timer prevents scrolling a widget for a few hundred milliseconds after a
                                  // scroll event if it was initiated over the canvas
    bool b_allowItemScroll;       // See above.
    int m_drawAction;

private slots:
    void slotAllowItemScroll()
    {
        b_allowItemScroll = true;
    }
};

/**
Abstract class for a "editing operation" on the ICNDocument, such as moving an item or resizing the canvas
@author David Saxton
*/
class CanvasManipulator : public QObject
{
    Q_OBJECT

public:
    CanvasManipulator(ItemDocument *itemDocument, CMManager *cmManager);
    ~CanvasManipulator() override;
    enum Type { RepeatedItemAdd, RightClick, AutoConnector, ManualConnector, ItemMove, ItemResize, MechItemMove, Select, CanvasResize, ItemDrag, Draw };
    virtual Type type() const = 0;
    /**
     * Called when the user single-clicks the mouse
     * @returns true if the manipulation operation has finished
     */
    virtual bool mousePressedInitial(const EventInfo & /*info*/)
    {
        return false;
    }
    /**
     * Called when the user single-clicks the mouse after the first time (only
     * applicable for those operations who are not oneClick
     * @returns true if the manipulation operation has finished
     */
    virtual bool mousePressedRepeat(const EventInfo & /*info*/)
    {
        return false;
    };
    /**
     * Called when the user moves the mouse
     * @returns true if the manipulation operation has finished
     */
    virtual bool mouseMoved(const EventInfo & /*info*/)
    {
        return false;
    };
    /**
     * Called when the user releases the mouse
     * @returns true if the manipulation operation has finished
     */
    virtual bool mouseReleased(const EventInfo & /*info*/)
    {
        return true;
    }
    /**
     * Snaps the point to the 8-sized canvas grid.
     */
    static QPoint snapPoint(QPoint point);

protected slots:
    /**
     * Called when the working canvas emits a resized signal.
     */
    virtual void canvasResized(const QRect &oldSize, const QRect &newSize)
    {
        (void)oldSize;
        (void)newSize;
    }

protected:
    Type m_type;
    EventInfo m_eventInfo;
    QPoint m_prevPos;
    ItemDocument *p_itemDocument;
    ICNDocument *p_icnDocument;
    MechanicsDocument *p_mechanicsDocument;
    KtlQCanvas *p_canvas;
    ItemGroup *p_selectList;
    CNItemGroup *p_cnItemSelectList;
    MechanicsGroup *p_mechItemSelectList;
    CNItem *p_cnItemClickedOn;
    MechanicsItem *p_mechanicsItemClickedOn;
    CMManager *p_cmManager;
};

/**
@author David Saxton
*/
class CMRepeatedItemAdd : public CanvasManipulator
{
public:
    CMRepeatedItemAdd(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMRepeatedItemAdd() override;
    Type type() const override
    {
        return RepeatedItemAdd;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mousePressedRepeat(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
};

/**
@author David Saxton
*/
class CMRightClick : public CanvasManipulator
{
public:
    CMRightClick(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMRightClick() override;
    Type type() const override
    {
        return RightClick;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
};

/**
@author David Saxton
*/
class ConnectorDraw : public CanvasManipulator
{
public:
    ConnectorDraw(ItemDocument *itemDocument, CMManager *cmManager);
    ~ConnectorDraw() override;

    /**
     * Returns the colour used to indicate that the current connection
     * being drawn is valid. Invalid colour is black.
     */
    static QColor validConnectionColor();

protected:
    /**
     * If the node has more than 2 connections, return one of the
     * connectors
     */
    Connector *toConnector(Node *node);
    /**
     * Converts the given qcanvasitem to an appropriate node or connector.
     * @param posIsExact if true, then only gets an appropriate node or
     * connector when the to-be end-point of the new connector will coincide
     * with pos (i.e. auto-connector will call this with posIsExact = false,
     * and manual-connector will call this with posIsExact = true).
     */
    void grabEndStuff(KtlQCanvasItem *endItem, const QPoint &pos, bool posIsExact);
    /**
     * Returns the closest point to the clickPos that is on the given
     * connector.
     */
    QPoint toValidPos(const QPoint &clickPos, Connector *clickedConnector) const;

    QPointer<Node> p_startNode;
    QPointer<Connector> p_startConnector;
    Node *p_endNode;
    Connector *p_endConnector;
    QPoint startConnectorPoint;
};

/**
@author David Saxton
*/
class CMAutoConnector : public ConnectorDraw
{
public:
    CMAutoConnector(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMAutoConnector() override;
    Type type() const override
    {
        return AutoConnector;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    KtlQCanvasLine *m_connectorLine;
};

/**
@author David Saxton
*/
class CMManualConnector : public ConnectorDraw
{
public:
    CMManualConnector(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMManualConnector() override;
    Type type() const override
    {
        return ManualConnector;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mousePressedRepeat(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    ConnectorList m_fixedRouteConnectors;
    ManualConnectorDraw *m_manualConnectorDraw;
};

/**
@author David Saxton
*/
class CMItemMove : public CanvasManipulator
{
public:
    CMItemMove(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMItemMove() override;
    Type type() const override
    {
        return ItemMove;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;
    bool mousePressedRepeat(const EventInfo &info) override;

protected:
    void canvasResized(const QRect &oldSize, const QRect &newSize) override;
    void scrollCanvasToSelection();

    QPoint m_prevSnapPoint;
    bool m_bItemsSnapToGrid; ///< true iff selection contains CNItems
    int m_dx;
    int m_dy;
    ConnectorList m_translatableConnectors;
    NodeGroupList m_translatableNodeGroups;
    FlowContainer *p_flowContainerCandidate;
};

/**
@author David Saxton
*/
class CMItemResize : public CanvasManipulator
{
public:
    CMItemResize(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMItemResize() override;
    Type type() const override
    {
        return ItemResize;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    ResizeHandle *p_resizeHandle;
    double m_rh_dx;
    double m_rh_dy;
};

/**
@author David Saxton
*/
class CMMechItemMove : public CanvasManipulator
{
public:
    CMMechItemMove(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMMechItemMove() override;
    Type type() const override
    {
        return MechItemMove;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    uint m_prevClickedOnSM; // Previous select mode of the item that was clicked on
};

/**
@author David Saxton
*/
class SelectRectangle
{
public:
    SelectRectangle(int x, int y, int w, int h, KtlQCanvas *qcanvas);
    ~SelectRectangle();

    void setSize(int w, int h);
    KtlQCanvasItemList collisions();

protected:
    KtlQCanvasLine *m_topLine;
    KtlQCanvasLine *m_rightLine;
    KtlQCanvasLine *m_bottomLine;
    KtlQCanvasLine *m_leftLine;
    const int m_x;
    const int m_y;
    int m_w;
    int m_h;
    int m_prevCollisions_w;
    int m_prevCollisions_h;
    KtlQCanvasItemList m_prevCollisions;
};

/**
@author David Saxton
*/
class CMSelect : public CanvasManipulator
{
public:
    CMSelect(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMSelect() override;
    Type type() const override
    {
        return Select;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    SelectRectangle *m_selectRectangle;
};

/**
@author David Saxton
*/
class CMItemDrag : public CanvasManipulator
{
public:
    CMItemDrag(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMItemDrag() override;
    Type type() const override
    {
        return ItemDrag;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    bool b_dragged;
};

/**
@author David Saxton
A KtlQCanvasEllipse that uses a pen (not a brush) to paint
*/
class CanvasEllipseDraw : public KtlQCanvasEllipse
{
public:
    CanvasEllipseDraw(int x, int y, KtlQCanvas *canvas);

protected:
    void drawShape(QPainter &p) override;
};

/**
@author David Saxton
*/
class CMDraw : public CanvasManipulator
{
public:
    CMDraw(ItemDocument *itemDocument, CMManager *cmManager);
    ~CMDraw() override;
    Type type() const override
    {
        return Draw;
    }

    static CanvasManipulator *construct(ItemDocument *itemDocument, CMManager *cmManager);
    static ManipulatorInfo *manipulatorInfo();
    static bool acceptManipulation(uint eventState, uint cmState, uint itemType, uint cnItemType);

    bool mousePressedInitial(const EventInfo &info) override;
    bool mouseMoved(const EventInfo &info) override;
    bool mouseReleased(const EventInfo &info) override;

protected:
    KtlQCanvasRectangle *m_pDrawRectangle;
    CanvasEllipseDraw *m_pDrawEllipse;
    KtlQCanvasLine *m_pDrawLine;
};

/**
@author David Saxton
*/
class ManualConnectorDraw
{
public:
    ManualConnectorDraw(ICNDocument *_icnDocument, const QPoint &initialPos);
    virtual ~ManualConnectorDraw();

    /**
     * Called when the mouse is moved.
     * Normally will do something like updating the connector route
     */
    void mouseMoved(const QPoint &pos);
    /**
     * Called when the user clicks the mouse. If the connector finishes on a
     * valid KtlQCanvasItem (Node or Connetor), then this is returned. Otherwise,
     * null is returned.
     */
    KtlQCanvasItem *mouseClicked(const QPoint &pos);
    /**
     * Returns the list of points that define the manual connection route
     */
    QPointList pointList();
    /**
     * Sets the colour used to draw the connection lines.
     */
    void setColor(const QColor &color);

protected:
    void updateConnectorEnds();

    QList<KtlQCanvasLine *> m_connectorLines;
    ICNDocument *icnDocument;

    bool b_currentVertical;
    bool b_orientationDefined;

    QPoint m_initialPos;
    QPoint m_previousPos;
    QPoint m_currentPos;

    KtlQCanvasLine *m_currentCon;
    KtlQCanvasLine *m_previousCon;

    // The first item that we clicked on
    KtlQCanvasItem *p_initialItem;

    QColor m_color;
};

#endif
