/***************************************************************************
 *   Copyright (C) 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 ICNDOCUMENT_H
#define ICNDOCUMENT_H

#include "itemdocument.h"

#include <QMap>

class Cells;
class CNItem;
class CNItemGroup;
class Connector;
class ECNode;
class FlowContainer;
class Node;
class NodeGroup;

typedef QMap<QString, Node *> NodeMap;
typedef QList<QPointer<Connector>> ConnectorList;
typedef QList<QPointer<Node>> NodeList;
typedef QList<NodeGroup *> NodeGroupList;
typedef QList<QPointer<NodeGroup>> GuardedNodeGroupList;

/**
@author David Saxton
*/
class ICNDocument : public ItemDocument
{
    Q_OBJECT
public:
    ICNDocument(const QString &caption);

    ~ICNDocument() override;

    enum hit_score { hs_none = 0, hs_connector = 4, hs_item = 1000 };

    View *createView(ViewContainer *viewContainer, uint viewAreaId) override;

    /**
     * Will attempt to create an item with the given id at position p. Some item
     * (such as PIC/START) have restrictions, and can only have one instance of
     * themselves on the canvas, and adds the operation to the undo list
     */
    Item *addItem(const QString &id, const QPoint &p, bool newItem) override;

    /**
     * short for casting whatever itemWithID(id) returns
     */
    CNItem *cnItemWithID(const QString &id);
    /**
     * Returns a pointer to a node on the canvas with the given id,
     * or nullptr if no such node exists
     */
    virtual Node *nodeWithID(const QString &id) = 0;
    /**
     * Returns a pointer to a Connector on the canvas with the given id,
     * or nullptr if no such Connector exists
     */
    Connector *connectorWithID(const QString &id);
    /**
     * Adds a KtlQCanvasItem to the delete list to be deleted,
     * when flushDeleteList() is called
     */
    void appendDeleteList(KtlQCanvasItem *qcanvasItem) override;
    /**
     * Permantly deletes all items that have been added to the delete list with
     * the appendDeleteList( KtlQCanvasItem *qcanvasItem ) function.
     */
    void flushDeleteList() override = 0;
    /**
     * Reinherit this function to perform special checks on whether the two
     * given QCanvasItems (either nodes or connectors or both) can be
     * connected together.
     */
    virtual bool canConnect(KtlQCanvasItem *qcanvasItem1, KtlQCanvasItem *qcanvasItem2) const;
    /**
     *        copies the selected items to the clipboard, in an XML text form
     */
    void copy() override;
    /**
     *        selects everything in the current document
     */
    void selectAll() override;

    /**
     * registers (adds to the document) an item (a connector or a node)
     * @param qcanvasItem the item to be registered
     * @return true if succeeded, false if it didn't
     */
    bool registerItem(KtlQCanvasItem *qcanvasItem) override;
    /**
     * Returns a pointer to the 2-dimension array of ICNDocument cells.
     */
    Cells *cells() const
    {
        return m_cells;
    }
    /**
     * Adds score to the cells at the given cell referece
     */
    void addCPenalty(int x, int y, int score);
    /**
     * If there are two connectors joined to a node, then they can be merged
     * into one connector. The node will not be removed.
     * @param node The node between the two connectors
     * @param noCreate If true, no new connectors will be created
     * @returns true if it was successful in merging the connectors
     */
    // bool joinConnectors( Node *node );
    /**
     * Snaps a coordinate in the document to the grid
     * @param pos The coordinate
     * @return The snapped to grid coordinate
     */
    static int gridSnap(int pos); /// Returns 'pos' when snapped to grid
    /**
     * Snaps a point to the grid
     * @param pos The point
     * @return The adjusted coordinate
     */
    static QPoint gridSnap(const QPoint &pos);
    /**
     * Returns true if the CNItem is valid - e.g. will return true for a
     * component in a circuit, but not in a pic program
     */
    bool isValidItem(Item *item) override = 0;
    bool isValidItem(const QString &itemId) override = 0;

    // TODO to document
    virtual ConnectorList getCommonConnectors(const ItemList &list);
    virtual NodeList getCommonNodes(const ItemList &list);

    /**
     * returns all the nodes contained by the document. Note that this function is inefficient,
     * so don't use it in loops
     * @return all the nodes contained by the document
     */
    virtual NodeList nodeList() const = 0;

    /**
     * @return all the connectors from the document
     */
    const ConnectorList &connectorList() const
    {
        return m_connectorList;
    }

    /**
     * @return all the nodegroups from the document
     */
    const GuardedNodeGroupList &nodeGroupList() const
    {
        return m_nodeGroupList;
    }

    /**
     * @return the selected items from the document
     */
    ItemGroup *selectList() const override;

    /**
     * Creates a connector between two nodes, and returns a pointer to it
     * and adds the operation to the undo list
     */
    virtual Connector *createConnector(const QString &startNodeId, const QString &endNodeId, QPointList *pointList = nullptr) = 0;
    /**
     * Creates a connector from node1 to node2. If pointList is non-null, then the
     * connector will be assigned those points
     */
    // virtual

    Connector *createConnector(Node *node1, Node *node2, QPointList *pointList = nullptr);
    /**
     * Splits Connector con into two connectors at point pos2, and creates a connector from the node
     * to the intersection of the two new connectors. If pointList is non-null, then the new connector
     * from the node will be assigned those points
     */
    virtual Connector *createConnector(Node *node, Connector *con, const QPoint &pos2, QPointList *pointList = nullptr) = 0;
    /**
     * Splits con1 and con2 into two new connectors each at points pos1 and pos2, and creates a new connector
     * between the two points of intersection given by pos1 and pos2. If pointList is non-null, then the new
     * connector between the two points will be assigned those points
     */
    virtual Connector *createConnector(Connector *con1, Connector *con2, const QPoint &pos1, const QPoint &pos2, QPointList *pointList = nullptr) = 0;
    /**
     * Returns the flowcontainer at the given position at the highest level that
     * is not in the current select list, or nullptr if there isn't one
     */
    FlowContainer *flowContainer(const QPoint &pos);
    /**
     * Sets the drag (e.g. horizontal arrow) cursor for resizing a CNItem, depending on the corner clicked on
     */
    void setItemResizeCursor(int cornerType);

    void getTranslatable(const ItemList &itemList, ConnectorList *fixedConnectors = nullptr, ConnectorList *translatableConnectors = nullptr, NodeGroupList *translatableNodeGroups = nullptr);

    /**
     * Reroutes invalidated directors. You shouldn't call this function
     * directly - instead use ItemDocument::requestEvent.
     */
    void rerouteInvalidatedConnectors();
    /**
     * Assigns the orphan nodes into NodeGroups. You shouldn't call this
     * function directly - instead use ItemDocument::requestEvent.
     */
    virtual void slotAssignNodeGroups();

    void unregisterUID(const QString &uid) override;

public slots:
    /**
     * Deletes all items in the selected item list, along with associated
     * connectors, etc, and adds the operation to the undo list
     */
    void deleteSelection() override;
    /**
     * This function looks at all the connectors and the nodes, determines
     * which ones need rerouting, and then reroutes them
     */
    void requestRerouteInvalidatedConnectors();
    /**
     * Remaps the 2-dimension array of ICNDocument cells, and the various
     * hitscores / etc associated with them. This is used for connector
     * routing, and should be called after e.g. items have been moved
     */
    void createCellMap();
    /**
     * Call this to request NodeGroup reassignment.
     */
    void slotRequestAssignNG();

signals:
    /**
     * Emitted when a Connector is added
     */
    void connectorAdded(Connector *connector);
    /**
     * Emitted when a Node is added
     */
    void nodeAdded(Node *node);

protected:
    /**
     * Adds all connector points from the items (used in connector routing).
     * This only needs to be called when connector(s) need routing.
     */
    void addAllItemConnectorPoints();

    void fillContextMenu(const QPoint &pos) override;
    /**
     * Creates a new NodeGroup to control the node, if there does not already
     * exist a NodeGroup containing the given node. The associated nodes will
     * also be added to the NodeGroup.
     * @returns a pointer to the NodeGroup if one was created, or a pointer to the existing one containing that node
     */
    NodeGroup *createNodeGroup(Node *node);
    /**
     * Finds (and deletes if found) the NodeGroup containing the given node.
     * @returns true if the NodeGroup was found and deleted
     */
    bool deleteNodeGroup(Node *node);

    friend class CanvasEditor;

    /**
     *        deletes all the elements containde in the nodeList. Should be overridden.
     */
    virtual void deleteAllNodes() = 0;

    /**
     *        Selects all nodes on the document. Should be overridden.
     */
    virtual void selectAllNodes() = 0;

    // this should be overridden in {Flow|Circuit}ICNDocument
    ConnectorList m_connectorList;
    CNItemGroup *m_selectList; // Selected objects

    // OVERLOADED
    KtlQCanvasItemList m_itemDeleteList; // List of canvas items to be deleted

private:
    Cells *m_cells;
    GuardedNodeGroupList m_nodeGroupList;
};

/**
@author David Saxton
*/
class DirCursor
{
public:
    static DirCursor *self();
    ~DirCursor();

    static QPixmap leftArrow()
    {
        return self()->m_leftArrow;
    }

    static QPixmap rightArrow()
    {
        return self()->m_rightArrow;
    }

    static QPixmap upArrow()
    {
        return self()->m_upArrow;
    }

    static QPixmap downArrow()
    {
        return self()->m_downArrow;
    }

protected:
    DirCursor();
    void initCursors();

    static DirCursor *m_self;
    QPixmap m_leftArrow;
    QPixmap m_rightArrow;
    QPixmap m_upArrow;
    QPixmap m_downArrow;
};

#endif
