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

#include "itemdocument.h"
#include "variant.h"

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

class Document;
class EventInfo;
class Item;
class ItemData;
class ItemDocument;
class ItemView;
class DoubleSpinBox;
class Document;
class Variant;
class QBitArray;
class QTimer;

typedef Variant Property;

typedef Item *(*createItemPtr)(ItemDocument *itemDocument, bool newItem, const char *id);
typedef QPointer<Item> GuardedItem;
typedef QMap<QString, Variant *> VariantDataMap;
typedef QList<GuardedItem> ItemList;

/**
@author David Saxton
@author Daniel Clarke
*/
class Item : /* public QObject, */ public KtlQCanvasPolygon
{
    Q_OBJECT
public:
    Item(ItemDocument *itemDocument, bool newItem, const QString &id);
    ~Item() override;

    /**
     * @return Pointer to the VariantMap used for internal data storage
     */
    VariantDataMap *variantMap()
    {
        return &m_variantData;
    }

    double dataDouble(const QString &id) const;
    int dataInt(const QString &id) const;
    bool dataBool(const QString &id) const;
    QString dataString(const QString &id) const;
    QColor dataColor(const QString &id) const;

    virtual Property *createProperty(const QString &id, Variant::Type::Value type);
    Property *property(const QString &id) const;
    bool hasProperty(const QString &id) const;

    /**
     * Whether or not we can resize the item
     */
    virtual bool canResize() const
    {
        return false;
    }
    /**
     * Returns whether the CNItem allows itself to be moved on the canvas.
     * Most do, but some (such as the PicItem) don't allow this
     */
    virtual bool isMovable() const
    {
        return true;
    }
    /**
     * Returns whether or not what the item is displaying has (possibly) changed
     * since this function was last called. If your item doesn't move, yet still
     * continously changes what is being displayed (such as a seven segment
     * display or a lamp), then set m_bDynamicContent to be true in the
     * constructor or reinherit this to return true when the contents of the
     * item have changed since this function was last called.
     */
    virtual bool contentChanged() const
    {
        return m_bDynamicContent;
    }
    /**
     * Returns a identifier for the CNItem, which is unique on the ICNDocument
     */
    QString id() const
    {
        return m_id;
    }
    QString type() const
    {
        return m_type;
    }
    /**
     * @return the font used for drawing items. This is taken to be the
     * standard desktop font, limited to a size of 12 pixels.
     */
    QFont font() const;
    /**
     * Called from ItemLibrary after this class and subclasses have finished
     * constructing themselves.
     */
    virtual void finishedCreation();
    /**
     * Sets the selected flag of the item to yes. selected or unselected will be
     * emitted as appropriate
     */
    void setSelected(bool yes) override;
    /**
     * Convenience function for setting the item bounding area as changed on the
     * canvas
     */
    void setChanged();
    /**
     * Sets this item as a child of the given item. Calls reparented with the
     * old and the new parent.
     */
    void setParentItem(Item *parentItem);
    /**
     * The parent item for this item, or nullptr if none
     */
    Item *parentItem() const
    {
        return p_parentItem;
    }
    ItemDocument *itemDocument() const
    {
        return p_itemDocument;
    }
    /**
     * Returns the number of items away from the top item this is
     * (parent-wise). Returns 0 if has no parent.
     */
    int level() const;
    /**
     * If true, then adds ItemDocument::Z::(RaisedItem-Item) to the z value of
     * the item.
     */
    void setRaised(bool isRaised);
    /**
     * @Returns whether raised or not
     */
    bool isRaised() const
    {
        return m_bIsRaised;
    }
    /**
     * Sets this item to the given baseZ level, and calls this function for the
     * children with baseZ incremented by one. Reinherit this function to set
     * the Z of attached stuff (such as nodes).
     */
    virtual void updateZ(int baseZ);
    /**
     * Returns the item's position in the overall z-stack of items.
     */
    int baseZ() const
    {
        return m_baseZ;
    }
    /**
     * Adds a child. Calls the virtual function childAdded.
     */
    void addChild(Item *child);
    /**
     * Returns the list of children.
     * @param includeGrandChildren if includeGrandChildren is true then this list will also contain
     * the children's children, and so on recursively, instead of just the
     * immediate children.
     */
    ItemList children(bool includeGrandChildren = false) const;
    /**
     * Returns whether we have the given child as either a direct child, or as
     * either a direct or indirect child
     */
    bool contains(Item *item, bool direct = false) const;
    /**
     * Calls prePresize with the bounds, and if that returns true, sets
     * m_sizeRect to the given rect, and then calls postResize. The center of
     * \p sizeRect is taken as the point of rotation.
     * @param forceItemPoints if true, will set the item points to a rectangle
     * @of the given size. Pass true if you have already set the size, and want
     * to update the appearance and bounding of the item.
     */
    void setSize(QRect sizeRect, bool forceItemPoints = false);
    /**
     * Convenience function.
     * @see setSize( QRect sizeRect, bool forceItemPoints );
     */
    void setSize(int x, int y, int w, int h, bool forceItemPoints = false)
    {
        setSize(QRect(x, y, w, h), forceItemPoints);
    }
    /**
     * @returns the m_sizeRect rectangble that contains the item points
     */
    QRect sizeRect() const
    {
        return m_sizeRect;
    }
    /**
     * Reinherit this function if you want to determine what the minimum size is
     * that this item can be resized to.
     */
    virtual QSize minimumSize() const
    {
        return QSize(0, 0);
    }
    int offsetX() const
    {
        return m_sizeRect.x();
    }
    int offsetY() const
    {
        return m_sizeRect.y();
    }
    int width() const
    {
        return m_sizeRect.width();
    }
    int height() const
    {
        return m_sizeRect.height();
    }
    virtual bool mousePressEvent(const EventInfo &eventInfo);
    virtual bool mouseReleaseEvent(const EventInfo &eventInfo);
    virtual bool mouseDoubleClickEvent(const EventInfo &eventInfo);
    virtual bool mouseMoveEvent(const EventInfo &eventInfo);
    virtual bool wheelEvent(const EventInfo &eventInfo);
    virtual void enterEvent(QEvent *);
    virtual void leaveEvent(QEvent *);
    /**
     * Returns the name of the CNItem, e.g. "Resistor"
     */
    QString name() const
    {
        return m_name;
    }
    /**
     * Modifies the exponent of the number so that it appears readable:
     * eg 10000->10, 174822->175, 0.6->600, etc
     */
    static int getNumberPre(double num);
    /**
     * Returns the SI exponent of the number as a letter:
     * eg 10000 returns 'k', 0.6 returns 'm', etc
     */
    static QString getNumberMag(double num);
    /**
     * Returns the multiplier required to get the num up to human readable form:
     * eg 10000 returns 0.001, etc
     */
    static double getMultiplier(double num);
    /**
     * Returns the multiplier required to get the num from human readable form
     * to its actual value based on the SI exponent:
     * eg 'm' returns 0.001, etc
     */
    static double getMultiplier(const QString &mag);

    virtual ItemData itemData() const;
    virtual void restoreFromItemData(const ItemData &itemData);

public slots:
    virtual void removeItem();
    /**
     * Moves item - use this instead of moveBy() so that associated Nodes also get moved
     */
    void moveBy(double dx, double dy) override;
    /**
     * Removes a child. Calls the virtual function childRemoved
     */
    void removeChild(Item *child);

signals:
    /**
     * Emitted when the CNItem is removed. Normally, this signal is caught by associated
     * nodes, who will remove themselves as well.
     */
    void removed(Item *item);
    /**
     * Emitted when the item is selected or unselected.
     */
    void selectionChanged();
    /**
     * Emitted when the item is resized (after calling postResize)
     */
    void resized();
    /**
     * Emitted when the item is moved (by dx, dy).
     */
    void movedBy(double dx, double dy);

protected slots:
    virtual void propertyChangedInitial();
    virtual void dataChanged() {};

protected:
    /**
     * Reinherit this function if you want to do anything with children. Called
     * after the parent is changed, with the old parent and the new parent.
     */
    virtual void reparented(Item * /*oldParent*/, Item * /*newParent*/) {};
    /**
     * Reinherit this function if you want to do anything with children. Called
     * after a child has been added.
     */
    virtual void childAdded(Item *) {};
    /**
     * Reinherit this function if you want to do anything with children. Called
     * after a child has been removed.
     */
    virtual void childRemoved(Item *) {};
    /**
     * Set the rough bounding points for this item. Calls itemPointsChanged
     * after setting the points
     */
    void setItemPoints(const QPolygon &pa, bool setSizeFromPoints = true);
    /**
     * Reinherit this function if you want to apply any sort of transformation
     * to the item points
     */
    virtual void itemPointsChanged();
    virtual bool preResize(QRect sizeRect)
    {
        Q_UNUSED(sizeRect);
        return true;
    }
    virtual void postResize() {};

    QString m_id;
    QString m_name; ///< Name (e.g. "Resistor")
    QString m_type;
    GuardedItem p_parentItem; // If attached to a parent item
    ItemList m_children;
    QPointer<ItemDocument> p_itemDocument;
    QPolygon m_itemPoints;           // The unorientated and unsized item points
    QTimer *m_pPropertyChangedTimer; ///< Single show timer for one a property changes

    friend class ItemLibrary;

    int m_baseZ;
    bool m_bIsRaised;
    bool m_bDoneCreation;
    bool b_deleted;
    bool m_bDynamicContent;
    QRect m_sizeRect;
    VariantDataMap m_variantData;
};

#endif
