#ifndef JSON_H
#define JSON_H


#include <QVariant>
#include <QString>
#include <QQueue>

/**********************************************
 * based on: https://github.com/qt-json/qt-json
 */

/**
 * \namespace JSON
 * \brief A JSON data parser
 *
 * Json parses a JSON data into a QVariant hierarchy.
 */
namespace JSON {
    typedef QVariantMap JsonObject;
    typedef QVariantList JsonArray;


    /**
     * Clone a JSON object (makes a deep copy)
     *
     * \param data The JSON object
     */
    QVariant clone(const QVariant &data);

    /**
     * Insert value to JSON object (QVariantMap)
     *
     * \param v The JSON object
     * \param key The key
     * \param value The value
     */
    void insert(QVariant &v, const QString &key, const QVariant &value);

    /**
     * Append value to JSON array (QVariantList)
     *
     * \param v The JSON array
     * \param value The value
     */
    void append(QVariant &v, const QVariant &value);

    /**
     * Parse a JSON string
     *
     * \param json The JSON data
     */
    QVariant parse(const QString &json);

    /**
     * Parse a JSON string
     *
     * \param json The JSON data
     * \param success The success of the parsing
     */
    QVariant parse(const QString &json, bool &success);

    /**
     * This method generates a textual JSON representation
     *
     * \param data The JSON data generated by the parser.
     *
     * \return QByteArray Textual JSON representation in UTF-8
     */
    QByteArray serialize(const QVariant &data);

    /**
     * This method generates a textual JSON representation
     *
     * \param data The JSON data generated by the parser.
     * \param success The success of the serialization
     *
     * \return QByteArray Textual JSON representation in UTF-8
     */
    QByteArray serialize(const QVariant &data, bool &success, int _level = 0);

    /**
     * This method generates a textual JSON representation
     *
     * \param data The JSON data generated by the parser.
     *
     * \return QString Textual JSON representation
     */
    QString serializeStr(const QVariant &data);

    /**
     * This method generates a textual JSON representation
     *
     * \param data The JSON data generated by the parser.
     * \param success The success of the serialization
     *
     * \return QString Textual JSON representation
     */
    QString serializeStr(const QVariant &data, bool &success, int _level = 0);

    /**
     * This method sets date(time) format to be used for QDateTime::toString
     * If QString is empty, Qt::TextDate is used.
     *
     * \param format The JSON data generated by the parser.
     */
    void setDateTimeFormat(const QString& format);
    void setDateFormat(const QString& format);

    /**
     * This method gets date(time) format to be used for QDateTime::toString
     * If QString is empty, Qt::TextDate is used.
     */
    QString getDateTimeFormat();
    QString getDateFormat();

    /**
     * @brief setPrettySerialize enable/disabled pretty-print when serialize() a json
     * @param enabled
     */
    void setPrettySerialize(bool enabled);

    /**
     * @brief isPrettySerialize check if is enabled pretty-print when serialize() a json
     * @return
     */
    bool isPrettySerialize();



    /**
     * QVariant based Json object
     */
    class Object : public QVariant {
        template<typename T>
        Object& insertKey(Object* ptr, const QString& key) {
            T* p = (T*)ptr->data();
            if (!p->contains(key)) p->insert(key, QVariant());
            return *reinterpret_cast<Object*>(&p->operator[](key));
        }
        template<typename T>
        void removeKey(Object *ptr, const QString& key) {
            T* p = (T*)ptr->data();
            p->remove(key);
        }
    public:
        Object() : QVariant() {}
        Object(const Object& ref) : QVariant(ref) {}

        Object& operator=(const QVariant& rhs) {
            /** It maybe more robust when running under Qt versions below 4.7 */
            QObject * obj = qvariant_cast<QObject *>(rhs);
            //  setValue(rhs);
            setValue(obj);
            return *this;
        }
        Object& operator[](const QString& key) {
            if (type() == QVariant::Map)
                return insertKey<QVariantMap>(this, key);
            else if (type() == QVariant::Hash)
                return insertKey<QVariantHash>(this, key);

            setValue(QVariantMap());

            return insertKey<QVariantMap>(this, key);
        }
        const Object& operator[](const QString& key) const {
            return const_cast<Object*>(this)->operator[](key);
        }
        void remove(const QString& key) {
            if (type() == QVariant::Map)
                removeKey<QVariantMap>(this, key);
            else if (type() == QVariant::Hash)
                removeKey<QVariantHash>(this, key);
        }
    };


    class BuilderJsonArray;

    /**
     * @brief The BuilderJsonObject class
     */
    class BuilderJsonObject {

        public:
            BuilderJsonObject();
            BuilderJsonObject(JsonObject &json);

            BuilderJsonObject *set(const QString &key, const QVariant &value);
            BuilderJsonObject *set(const QString &key, BuilderJsonObject *builder);
            BuilderJsonObject *set(const QString &key, BuilderJsonArray *builder);
            JsonObject create();

        private:
            static QQueue<BuilderJsonObject *> created_list;

            JsonObject obj;
    };


    /**
     * @brief The BuilderJsonArray class
     */
    class BuilderJsonArray {

        public:
            BuilderJsonArray();
            BuilderJsonArray(JsonArray &json);

            BuilderJsonArray *add(const QVariant &element);
            BuilderJsonArray *add(BuilderJsonObject *builder);
            BuilderJsonArray *add(BuilderJsonArray *builder);
            JsonArray create();

        private:
            static QQueue<BuilderJsonArray *> created_list;

            JsonArray array;
    };


    /**
     * @brief Create a BuilderJsonObject
     * @return
     */
    BuilderJsonObject *objectBuilder();

    /**
     * @brief Create a BuilderJsonObject starting from copy of another json
     * @return
     */
    BuilderJsonObject *objectBuilder(JsonObject &json);

    /**
     * @brief Create a BuilderJsonArray
     * @return
     */
    BuilderJsonArray *arrayBuilder();

    /**
     * @brief Create a BuilderJsonArray starting from copy of another json
     * @return
     */
    BuilderJsonArray *arrayBuilder(JsonArray &json);
}



#endif // JSON_H