/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2018 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#ifndef PROPERTY_H
#define PROPERTY_H

#include <QVariant>
#include <QStringList>
#include <QIcon>

#include "CamiTKAPI.h"

namespace camitk {

/**
 *
 *  @ingroup group_sdk_libraries_core_utils
 *
 *  @brief
 *  This class describes a property that can be used in components and actions or any class
 *  that needs to be passed to ObjectControler.
 *  A property has a type, a description (to be displayed for example as a tooltip,
 *  can be rich-text, see http://qt-project.org/doc/qt-4.8/richtext-html-subset.html for supported
 *  html tags), a value, a unit of measurement (SI unit if possible), and some specific attributes (that depends on the type,
 *  e.g. minimal and maximal values, single steps, number of decimals, regular expression...).
 *  An enum type can also be used for properties.
 *
 *  Properties can be grouped in subgroups, see Property::setGroupName().
 *
 *  Basically this is a way to overcome the Qt Meta Object properties limitations.
 *  A camitk::Property enriches a Qt Meta Object property (a very simplified
 *  Decorator Design Pattern).
 *
 *  Using camitk::Property instead of directly using Qt Meta Object property helps
 *  to build a better interactive GUI (in the property explorer for components and
 *  in the ActionWidget for actions).
 *  Note that a camitk::Property is represented as a regular Qt Meta Object property
 *  as well (the value of the camitk::Property is in fact stored by the Qt Meta Object property)
 *
 *  Here are some examples to get started with:
 *  \code
    // Demonstrates how rich text can be used in description
    addParameter(new Property("Bool Prop", false, "This a <i>normal</i> bool property.<br/><b>Note:</b> Rich text description!<br/>See also: <a href=\"http://camitk.imag.fr\">CamiTK web page</a>", ""));

    // beware: the action takes ownership of the Property pointer
    // This means that the line above does not generate memory leak.

    // Demonstrates how properties can be set as read-only
    // Demonstrates how properties can be grouped.
    // Here all read-only properties are assemble in the same eponymic group
    Property *readOnlyBool = new Property("Read Only Bool", true, "This a read-only boolean", "");
    readOnlyBool->setReadOnly(true);
    readOnlyBool->setGroupName("Read Only Properties");
    addParameter(readOnlyBool);

    // beware: the action takes ownership of the Property pointer
    // This means that you should NOT delete readOnlyBool in the action desctructor

    // Demonstrates how integer properties can be bounded
    Property *boundedInt = new Property("Bounded Int", 12, "An integer bounded between 0 and 20", "");
    boundedInt->setAttribute("minimum", 0);
    boundedInt->setAttribute("maximum", 20);
    boundedInt->setGroupName("Numeric Properties");
    addParameter(boundedInt);

    // Demonstrates how double properties can be half-bounded
    Property *doubleWithMax = new Property("Double With Max", -10.0, "A double with a max value of -4.2", "");
    doubleWithMax->setAttribute("maximum", -4.2);
    doubleWithMax->setGroupName("Numeric Properties");
    addParameter(doubleWithMax);

    Property *intWithSingleStep = new Property("Int With Single Step", -10, "An integer with a single step of <i>5</i>", "");
    intWithSingleStep->setAttribute("singleStep", 5);
    intWithSingleStep->setGroupName("Numeric Properties");
    addParameter(intWithSingleStep);

    Property *doubleWithStepAndDecimal = new Property("Double With Single Step And Precision", 3.14159, "A double with 5 decimals and a single step of 1.10<sup>-5</sup>", "");
    doubleWithStepAndDecimal->setAttribute("singleStep", 10e-6);
    doubleWithStepAndDecimal->setAttribute("decimals", 5);
    doubleWithStepAndDecimal->setGroupName("Numeric Properties");
    addParameter(doubleWithStepAndDecimal);

    Property *intWithDecimal = new Property("Int With Precision", 4, "An integer with a precision set to 5 decimals: this should not affect it.", "");
    intWithDecimal->setAttribute("decimals", 5);
    intWithDecimal->setGroupName("Numeric Properties");
    addParameter(intWithDecimal);

    Property *readOnlyQVector3D = new Property("Read Only QVector3D", QVector3D(-4.0, 2.0, 0.1), "A read-only QVector3D", "");
    readOnlyQVector3D->setReadOnly(true);
    readOnlyQVector3D->setGroupName("Read Only Properties");
    addParameter(readOnlyQVector3D);

    Property *stringWithRegExp = new Property("QString Constrained by RegExp", QString("loweronly"), "A QString contrained to lowercase characters only (no separators, numbers...)", "");
    stringWithRegExp->setAttribute("regExp",QRegExp("[a-z]*"));
    addParameter(stringWithRegExp);

    Property *constrainedQRect = new Property("Constrained QRect", QRect(10,10,20,20), "A QRect contrained to (0,0,50,50)", "");
    constrainedQRect->setAttribute("constraint", QRect(0,0,50,50));
    addParameter(constrainedQRect);

    Property *constrainedQVector3D = new Property("Constrained QVector3D", QVector3D(1.1, 2.2, 3.3), "A constrained QVector3D (not yet implemented)", "");
    constrainedQVector3D->setAttribute("constraint", QVector3D(10.0, 10.0, 10.0));
    addParameter(constrainedQVector3D);
    \endcode
 * @note
 * To create a new Property, prefer using Property.
 * To check if a Property has been added to your Component / Action, use either Component::getProperty() or Action::getProperty() methods.
 * To modify an existing Property's value, check if it exists:
 *  - if not, create a new instance of Property
 *  - if yes, directly modify its value by using QObject::setProperty() method.e
 *
 *  The GUI interaction is automatically build and managed by the class ObjectControler.
 *
 *  An example for adding properties to an action can be seen in tutorials/actions/properties.
 *  More specifically see the EnumPropertyExample action to learn about how to use enum properties.
 *
 *  The class PropComponent and PropAction in the tutorials demonstrates how to use camitk::Property
 *  instead of Qt Meta Object Property.
 *
 *  The available property types are:

    Property Type | Property Type Id
    ------------- | -----------------
    int           | QVariant::Int
    double        | QVariant::Double
    bool          | QVariant::Bool
    QString       | QVariant::String
    QVector3D     | QVariant::QVector3D
    QColor        | QVariant::Color
    QDate         | QVariant::Date
    QTime         | QVariant::Time
    QChar         | QVariant::Char
    QDateTime     | QVariant::DateTime
    QPoint        | Variant::Point
    QPointF       | QVariant::PointF
    QKeySequence  | QVariant::KeySequence
    QLocale       | QVariant::Locale
    QSize         | QVariant::Size
    QSizeF        | QVariant::SizeF
    QRect         | QVariant::Rect
    QRectF        | QVariant::RectF
    QSizePolicy   | QVariant::SizePolicy
    QFont         | QVariant::Font
    QCursor       | QVariant::Cursor
    enum          | enumTypeId()
    flag          | flagTypeId()
    group         | groupTypeId()

    * Possible attributes depends on the property type, mostly (see also QtVariantPropertyManager API doc):
    Property Type | Attribute Name | Attribute Type
    ------------- | :------------: | :------------:
      \c int      |    minimum     | QVariant::Int
      \c int      |    maximum     | QVariant::Int
      \c int      |   singleStep   | QVariant::Int
      \c double   |    minimum     | QVariant::Double
      \c double   |    maximum     | QVariant::Double
      \c double   |   singleStep   | QVariant::Double
      \c double   |    decimals    | QVariant::Int
        QString   |     regExp     | QVariant::RegExp
        QDate     |     minimum    | QVariant::Date
        QDate     |     maximum    | QVariant::Date
        QPointF   |   decimals     | QVariant::Int
        QSize     |    minimum     | QVariant::Size
        QSize     |    maximum     | QVariant::Size
        QSizeF    |    minimum     | QVariant::SizeF
        QSizeF    |    maximum     | QVariant::SizeF
        QSizeF    |    decimals    | QVariant::Int
        QRect     |   constraint   | QVariant::Rect
        QRectF    |   constraint   | QVariant::RectF
        QRectF    |    decimals    | QVariant::Int
       \c enum    |    enumNames   | QVariant::StringList  (note that this can be build automatically)
       \c flag    |   flagNames (NOT IMPLEMENTED YET)  | QVariant::StringList

    \note
    For enums, you need to do few things in the C++ class that has a enum typed property:
      - add Q_underscore_OBJECT macro in your class declaration
      - add the enum type in your class declaration
      - register your enum name using the Q_ENUM macro in your class declaration
      - register the enum type name to the property using the Property::setEnumTypeName

    \note
    If your Qt version is old (version < 5.5), then use Q_ENUMS instead of Q_ENUM and
    don't forget to register the enum as a new type using Q_DECLARE_METATYPE outside your class declaration
    as well.
    Using Qt >=5.5 and Q_ENUM is recommended.

    Anywhere in your code use the property(..).toInt() method to get the classical enum value of the property,
    and use Property::getEnumValueAsString() to get the enum value as a string.
    If you declared your enum using Q_ENUM (instead of deprecated Q_ENUMS), you can also use directly
    QMetaEnum::fromType to retrieve the enum value as strings (see below).

    You can the enum value names in the GUI using the "enumNames" attributes.
    There is also a way to automatically build nicer enumNames (\s

    Enum icons might be set using Property::setEnumIcons.

    For instance in the header:
    \code
        class MyAction : public camitk::Action {
            // Really needed! (replace ‗ by _ in your code if you copy-paste this snippet)
            Q‗OBJECT

            // declare the C++ enum
            enum MyEnum {
                PossibleValue1,
                PossibleValue2
            };

            // register the enum in Qt >= 5.5
            Q_ENUM(MyEnum)
            // Note: use the following line instead in Qt < 5.5 and don't forge the Q_DECLARE_METATYPE (see below)
            // Q_ENUMS(MyEnum)

            ...
            private:
            // the enum property
            Property *enumProp;
        };

        // Note: you also need to register the equivalent meta type for the new enum if Qt < 5.5
        // Q_DECLARE_METATYPE(MyAction::MyEnum);
    \endcode

    And then in the code:
    \code
        MyAction::MyAction(ActionExtension * extension) : Action(extension) {
            ...
            // build the dynamic prop based on the enumeration
            enumProp = new Property("My Enumeration", MyAction::PossibleValue2, "Enumeration support example","");

            // register the enum type name for automatically manage the enum as a popup list
            enumProp->setEnumTypeName("MyEnum",this);

            // The Property class automatically build the enum names presented to the user in the GUI
            // (it will changed the enum literals to get a cleaner look, e.g. PossibleValue1 becomes "Possible Value 1")

            // OR

            // Set the enum names of your choice, using the enumNames property:
            // enumProp->setEnumTypeName("MyEnum");
            // QStringList propGUIName;
            // propGUIName << "Possible Value #1" << "Possible Value #2";
            // enumProp->setAttribute("enumNames", propGUIName);

            // register the new prop as an action parameter
            addParameter(enumProp);
       }

       ...

            // get the value as classical C++ enum
            MyEnum enumPropCurrentValue = (MyEnum) property("My Enumeration").toInt();

            // get the value as a QString (either "PossibleValue1" or "PossibleValue2", beware: this is different from the GUI names)
            QString enumPropAsString = enumProp->getEnumValueAsString(this);

            // OR if you use the recommended Q_ENUM declaration:
            QMetaEnum metaEnum = QMetaEnum::fromType<EnumerationExample>();
            QString enumPropAsStringDirect = metaEnum.valueToKey(enumPropCurrentValue);
    \endcode

    \endcode
    \note
    This is not exactly a decorator pattern, as the Property class is not abstract. The Qt
    Meta Object is still held by the QtObject inherited class (e.g. Component or Action).
    The camitk::Property class adds description, readOnly status and specific attributes to
    a QObject dynamic property.
 */
class CAMITK_API Property {

public:
    /** Constructor.
     * The variant parameters also allows you to initialize the value of the property.
     * By default a Property is enabled and editable (i.e. by default it is not read-only)
     * @param name property name (unique identifier of your class property
     * @param variant specify the property type (QVariant) and initial value
     * @param description a sentence or two to describe the property (and its unit if any), can be Rich Text
     * @param unit a unit of measurement (in SI unit), use symbols from https://en.wikipedia.org/wiki/SI_base_unit or https://en.wikipedia.org/wiki/SI_derived_unit when possible
     */
    Property(QString name, const QVariant& variant, QString description, QString unit);

    /// Destructor
    virtual ~Property() = default;

    /// get the name of the property
    const QString& getName() const;

    /// return the initial (default) value
    const QVariant& getInitialValue() const;

    /// set this property as read-only
    void setReadOnly(bool);

    /// @return true only if this property is read-only
    bool getReadOnly() const;

    /// set the description (can be rich text)
    void setDescription(QString);

    /// get the description
    const QString& getDescription() const;

    /** Set a given property for this attribute.
     *  Note that not all the attributes are not usable for all property type (see table above).
     *  The supported attribute names are (see QtVariantPropertyManagerPrivate() constructor):
     *  - "maximum"
     *  - "minimum"
     *  - "singleStep"
     *  - "decimals"
     *  - "constraint"
     *  - "enumNames"
     *  - "regExp"
     *
     * @param attribute name of the attribute
     * @param value value of this attribute
     */
    void setAttribute(const QString& attribute, const QVariant& value);

    /** get the current value of a given attribute, see setAttribute()
     *  if the attribute attName was never set using setAttribute(), the return QVariant
     *  is invalid.
     *  To test if a QVariant is invalid, use the QVariant::isValid() method
     *  e.g.:
     *  if (!myProp.getAttribute("bad").isValid()) {
     *      CAMITK_INFO(tr("myProp does not have an attribute 'bad' (or this attribute is still equals to the default value)"))
     *  }
     *
     *  It is recommended to only use this method inside a foreach(QString s: getAttributeList())
     */
    QVariant getAttribute(QString attName);

    /// returns the list of attribute names that are specific to this property
    QStringList getAttributeList();

    /** if the property's type is an enum, set the name of the registered Qt Enum.
     *  The enum names that will appear in the GUI will have to be given by setting the "enumNames" attribute.
     */
    void setEnumTypeName(QString);

    /** if the property's type is an enum, set the name of the registered Qt Enum AND automatically build
     *  the enum names that will be used in the GUI.
     *  There is no need to set the "enumNames" attribute, the enum names will automatically be build from the enum literals:
     *      - all words will be capitalized
     *      - all "_" (underscore) will be transformed to space
     *
     *  @param enumTypeName the enum type name as declared in the header file
     *  @param objectDeclaringTheEnum is a pointer to the object instantiated from the class that declared the enum
     */
    void setEnumTypeName(QString enumTypeName, QObject* objectDeclaringTheEnum);

    /// @returns the name of the Qt registered enum if the property's type is an enum, the null string otherwise (can be tested against QString isNull() method)
    QString getEnumTypeName() const;

    /** Utility method to get the current property value as a string.
     *  The string corresponds one of the enum values: not the gui enum values set using setAttribute("enumNames"...)
     *
     *  If the property's type is an enum, this is the string corresponding to its value (the property value can be accessed, the normal way using the QVariant toInt() to get the int value (classical C++ enum value)
     * @param objectDeclaringTheEnum is a pointer to the object instantiated from the class that declared the enum
     * @return the enum value as QString.
     */
    QString getEnumValueAsString(const QObject* objectDeclaringTheEnum) const;

    /// get the enum icons
    QMap<int, QIcon> getEnumIcons() const;

    /// set the icons for all the enums
    void setEnumIcons(const QMap<int, QIcon>& enumIcons);

    /** Set the group name.
     *  Properties can be separated into subgroups. Just set the group name and they will be arranged/classified by group.
     *  @param groupName name of the group for this property
     */
    void setGroupName(QString groupName);

    /** get this property subgroup's name
     * @return the group name or the null QString if no group were set, it can be tested with QString::isNull().
     */
    QString getGroupName() const;

private:
    /// name of the property
    QString name;

    /// name of the group in which this property is classified (null if no group name were set)
    QString groupName;

    /** initial value of the property,
     *  Only needed between the time when the property is instantiated and the time it is created by the
     *  Component or Action as a Qt Meta Property. Once the Qt Meta Property is created, the value of
     *  the property is managed by the Qt Meta Property.
     */
    QVariant initialValue;

    /// description of the property, can be rich text
    QString description;

    /// is the property read only
    bool readOnly;

    /// map containing all the attributes and their values
    QMap<QString, QVariant> attributeValues;

    /// if the property's type is an enum, this is the Qt registered enum name, otherwise it is the null string
    QString enumTypeName;

    /// map containing all the icons for the enum
    QMap<int, QIcon> enumIcons;
};

}

#endif // PROPERTY_H
