/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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 __VARIANT_DATA_MODEL__
#define __VARIANT_DATA_MODEL__

#include <QVariant>

#include "CamiTKExtensionGeneratorAPI.h"

/**
 * @brief VariantDataModel encapsulates `QVariant` and can be used as a model for any type of `QVariant` supported data structure (map, list, atomic variant such as double, int, ...)
 *
 * Advantages over `QVariant`
 * - the data are accessed by reference to a `VariantDataModel`
 * - in-place adding/removing/modifying data keeps the reference unchanged while updating the encapsulated `QVariant` (e.g., adding a element in an array is seen as "in place" and is not done in a new array)
 * - STL algorithms can be used for list and maps
 *
 * VariantDataModel supports atomic (single value `QVariant` such as string, numeric value), list (using `QVariantList`) and map (using `QVariantMap`).
 *
 * VariantDataModel can be build directly from JSON string, JSON object, `QVariant`, `QVariantList` and `QVariantMap`.
 * VariantDataModel offers utility methods to access, insert, remove data in place
 *
 * ### Simple example ()
 *
 * \code {.cpp}
// use JSON string as input (you can also use JSON object, QVariantList, QVariantMap or QVariant)
QString rawInput = R"json({
  "name":"foo",
  "parameters":[
    {"name":"bar","value":14},
    {"name":"baz","value":"faz"}
  ]
})json";
VariantDataModel dataModel = VariantDataModel::fromJsonString(rawInput);

// get a ref to part of the data model
VariantDataModel& parameters = dataModel["parameters"];

// get values and modify in place
parameters[0]["value"] = parameters[0]["value"].getValue().toInt() / 4.0;
parameters[1]["value"] = "fuz";

// create from QVariantMap
VariantDataModel anotherParam = QVariantMap();

// add key/value
anotherParam["name"] = "camitk";
anotherParam["value"] = true;

// append to existing data
parameters.append(anotherParam);

// also available:
// remove(), removeAt(..)
\endcode
 *
 * ### Usage example
 *
\code {.cpp}
std::cout << "Name: " << dataModel["name"].toString().toStdString() << std::endl; // "foo"
for (unsigned i = 0; i < parameters.size(); i++) {
    std::cout << "- parameter " << i << ": name \"" << parameters[i]["name"].toString().toStdString() << "\", value: " << parameters[i]["value"].toString().toStdString() << std::endl;
}
\endcode
*
* Prints
*
\code {.txt}
Name: foo
- parameter 0: name "bar", value: 3.5
- parameter 1: name "baz", value: fuz
- parameter 2: name "camitk", value: true
\endcode
 *
 *
 * ### using STL
 *
\code {.cpp}
for (auto p : parameters) {
    std::cout << "- parameter name \"" << p["name"].toString().toStdString() << "\", value: " << p["value"].toString().toStdString() << std::endl;
}

QString parameterToFind = "camitk";
auto itList = std::find_if(parameters.begin(), parameters.end(), [ &parameterToFind ](VariantDataModel const & p) {
    return p["name"].toString() == parameterToFind;
});
std::cout << (itList != parameters.end()) << std::endl; // true

QString valueToFind = "foo";
auto itMap = std::find_if(dataModel.beginMap(), dataModel.endMap(), [&valueToFind](const QString & value) {
    return value == valueToFind;
});
std::cout << "Key \"" << it.key() << "\" has value \"" << valueToFind \"" << std::endl;
\endcode
 *
 * \note This class might need two additional members, depending on future usage:
 * - getErrorStatus():bool
 * - getErrorString():QString
 * instead of using std::cerr for warning and errors
 */
class CAMITKEXTENSIONGENERATOR_EXPORT VariantDataModel {
public:
    /// @brief The different types that can be stored in a VariantDataModel
    enum VariantNodeType {
        Atomic, ///< Atomic type (string, numeric values, boolean)
        List,   ///< List of VariantDataModel
        Map     ///< Map of <QString (key), VariantDataModel (value)>
    };

    /// @name constructors
    /// @{
    /// @brief Default constructor: build an atomic invalid VariantDataModel
    VariantDataModel();

    /// @brief Build an atomic VariantDataModel with the given variant value
    /// \note no type check are done, any QVariant apart from QVariantList and QVariantMap
    /// can be stored in an atomic VariantDataModel. The user is responsible to ensure
    /// this is coherent with its data model structure
    VariantDataModel(const QVariant& variant);

    /// @brief Build a list VariantDataModel from the given variant list
    /// It recursively build the VariantDataModel (hence if the given variant
    /// contains atomic, list or map elements their VariantDataModel representation
    /// will be inserted in the list)
    VariantDataModel(const QVariantList& variant);

    /// @brief Build a map VariantDataModel from the given variant map
    /// It recursively build the VariantDataModel (hence if the given variant
    /// contains atomic, list or map elements their VariantDataModel representation
    /// will be inserted in the map)
    VariantDataModel(const QVariantMap& variant);

    /// Copy constructor
    VariantDataModel(const VariantDataModel& other);
    ///@}

    /// @name static utility method
    /// @{
    /// Build the model from the given JSON string (must be representing a JSON object)
    static const VariantDataModel fromJsonString(const QString& jsonString);

    /// Build the model from the given JSON object
    static const VariantDataModel fromJsonObject(const QJsonObject& jsonObject);

    /// get the invalid VariantDataModel
    static VariantDataModel& invalid();
    /// @}

    /// @name informative methods
    /// @{
    /// true if there was data modification since the instantiation or the last reset
    bool isModified() const;
    /// true if this is the invalid VariantDataModel
    bool isValid() const ;
    /// either "Atomic", "List" or "Map"
    /// @return
    QString typeString() const;
    /// same as typeString() (unused)
    QString getName();
    /// @}

    ///@name set/get/test
    /// affectation to the given newValue (can be atomic, list or map)
    /// @{
    VariantDataModel& operator=(QVariant newValue);

    /// get the encapsulated value (can be atomic, list or map)
    QVariant getValue() const;

    /// Get the QVariant value as a string (for atomic type)
    /// \warning For list and map this will return an empty string
    /// (use toJsonString() if you want the json equivalent of a VariantDataModel)
    QString toString() const;

    /// utility function to automatically convert to QString in statement
    operator QString() const;

    /// Get the string representing this model in JSON format:
    /// - an Atomic returns the quoted value for strings (same as toString() but with quotes),
    ///   not quoted for any other atomic values
    /// - a List returns the equivalent JSON array (`"[ ..., ... ]"`)
    /// - a Map returns the equivalent JSON object (`"{ ..., ... }"`)
    QString toJsonString() const;

    /// Clear all encapsulated data
    /// As this method renders the data model invalid (same effect as = QVariant()),
    /// this VariantDataModel will therefore not be added to its parent
    /// in the subsequent getValue(), toString()...
    ///
    /// It is still present in the parent VariantDataModel (as there are no members
    /// that links the current VariantDataModel to its parent), but can be considered
    /// as removed as it won't be added to the value in any subsequent getValue(), toString()...
    void remove();

    /// equality test (use QVariant/QVariantList/QVariantMap == operator)
    bool operator==(const VariantDataModel& other) const;
    bool operator==(const QVariant& v) const;
    bool operator==(const QVariantList& v) const;
    bool operator==(const QVariantMap& v) const;

    /// inequality test (opposite of ==)
    bool operator!=(const VariantDataModel& other) const;
    bool operator!=(const QVariant& v) const;
    bool operator!=(const QVariantList& v) const;
    bool operator!=(const QVariantMap& v) const;

    /// Returned value depends on the VariantDataModel type:
    /// - For atomic: returns `(*this)`
    /// - For list: returns the last element of the list
    /// - For map: returns the last value of the map
    VariantDataModel& last();

    /// Returned value depends on the VariantDataModel type:
    /// - For atomic: returns 1 is this is not empty (i.e., if it is a valid VariantDataModel) otherwise returns 0
    /// - For list: returns the list size
    /// - For map: returns the map size
    int size() const;

    /// Returned value depends on the VariantDataModel type:
    /// - for atomic: returns true if this is an invalid VariantDataModel, false otherwise
    /// - for list: returns true if the list is empty
    /// - for map: returns true if the map is empty
    bool isEmpty() const;

    /// reset the modified flag (recursively for list and map)
    void reset();
    ///@}

    /// @name Map management
    /// This methods are useful when the VariantDataModel is a map
    /// @{

    /// Returned value depends on the VariantDataModel type:
    /// - for map: returns the value corresponding given key
    /// - for atomic or list: returns an invalid VariantDataModel
    ///
    /// \note for map: if the key is not in the map, it will be created
    /// and the returned value will be an invalid VariantDataModel
    VariantDataModel& value(const QString& key);

    /// Returned value depends on the VariantDataModel type:
    /// - For map: this is the same as value(...)
    /// - For list: if the key represents an int (e.g. "42"),
    ///   this methods acts as operator[](int i),
    ///   otherwise it returns an invalid VariantDataModel
    /// - For atomic value: returns an invalid VariantDataModel
    /// @return invalid variant if the key si not present in the map
    VariantDataModel& operator[](const QString& key);

    /// const version of operator[](QString)
    const VariantDataModel operator[](const QString& key) const;

    /// Returned value depends on the VariantDataModel type:
    /// - For map: returns true if the map contains the given key
    /// - For atomic or list: returns false
    bool contains(const QString& key) const;

    /// Behaviour depends on the VariantDataModel type:
    /// - For map: insert the given pair to the map
    /// - For atom and list: does nothing
    void insert(const QString& key, const VariantDataModel& value);

    /// Behaviour depends on the VariantDataModel type:
    /// - For map: insert the given pair to the map (with recursive encapsulation of the given variant)
    /// - For atom and list: does nothing
    void insert(const QString& key, const QVariant& value);

    /// Behaviour and returned value depends on the VariantDataModel type:
    /// - For map: removes all the item that have the given keyp and return the number of items removed
    ///   (1 if the key exists, 0 otherwise)
    /// - For atomic or list: does nothing, returns 0
    int remove(const QString& key) ;
    /// @}

    /// @name List management
    /// This methods are useful when the VariantDataModel is a List
    /// @{

    /// Returned value depends on the VariantDataModel type:
    /// - For list: returns the i-th element
    /// - For map and atomic value: returns an invalid VariantDataModel
    VariantDataModel& at(int i) ;

    /// same as at(int)
    VariantDataModel& operator[](int i);

    /// const version of operator[](int)
    const VariantDataModel operator[](int i) const;

    /// Behaviour and returned value depends on the VariantDataModel type:
    /// - For list: removes the first occurrence of the item with the given value
    ///   and returns true on success; otherwise returns false.
    /// - For atomic and map: does nothing, returns false
    bool removeOne(const VariantDataModel& item);

    /// Behaviour and returned value depends on the VariantDataModel type:
    /// - For list: removes the item at index position i.
    ///   If i is not a valid index (i.e., 0 <= i < size()), the VariantDataModel stays unmodified.
    ///   Return true on success; otherwise returns false.
    /// - for atomic and mpa: does nothing, returns false
    bool removeAt(int i);

    /// Behaviour depends on the VariantDataModel type:
    /// - for list: adds the given item to the list
    /// - for atomic and mpa: does nothing
    void append(const VariantDataModel& item);
    ///@}

    ///@name STL-style iterators
    /// @{

    /// Returns an STL-style iterator pointing to the first item in the list
    /// \note if the list is empty or if this VariantDataModel is not a list, this is the same as end()
    QList<VariantDataModel>::iterator begin();

    /// const version of begin()
    QList<VariantDataModel>::const_iterator begin() const;

    /// Returns an STL-style iterator pointing to the imaginary item after the last item in the list
    /// \note if the list is empty or if this VariantDataModel is not a list, this is the same as begin()
    QList<VariantDataModel>::iterator end();

    /// const version of end()
    QList<VariantDataModel>::const_iterator end() const;

    /// Returns a STL-style iterator pointing to the first item in the map
    /// \note if the map is empty or if this VariantDataModel is not a map, this is the same as endMap()
    QMap<QString, VariantDataModel>::iterator beginMap();

    /// const version of beginMap()
    QMap<QString, VariantDataModel>::const_iterator beginMap() const;

    /// Returns an STL-style iterator pointing to the imaginary item after the last item in the map.
    /// \note if the map is empty or if this VariantDataModel is not a map, this is the same as beginMap()
    QMap<QString, VariantDataModel>::iterator endMap();

    /// const version of endMap()
    QMap<QString, VariantDataModel>::const_iterator endMap() const;
    ///@}

protected:
    /// true if there was a modification in the value, item or pair since instanciation or last reset
    bool isModifiedFlag;

    /// type can be Atomic, List or Map depending on how the VariantDataModel was instantiated
    VariantNodeType type;

    /// name (unused, equals to typeString())
    QString name;

    /// The default VariantDataModel, an Atomic VariantDataModel with invalid status
    static VariantDataModel invalidVariantDataModel;

    /// set the name of the VariantDataModel
    void setName(const QString& name);

private:
    /// create the VariantDataModel map from the given QVariantMap (used in constructors)
    void copyMap(const QVariantMap&);

    /// create the VariantDataModel map from the given QVariantList (used in constructors)
    void copyList(const QVariantList&);

    /// for Atomic this is where the value is stored
    QVariant simpleValue;

    /// for list, this is where all items are stored
    QList<VariantDataModel> list;

    /// for map, this is where all pairs are stored
    QMap<QString, VariantDataModel> map;
};

#endif // __VARIANT_DATA_MODEL__