#pragma once

#include <functional>

#include "AbstractElement.h"
#include "Array.h"
#include "ReferenceCounter.h"
#include "Text.h"

namespace Framework
{
    namespace JSON
    {
        class JSONArray;
        class JSONObject;
        class JSONBool;
        class JSONNumber;
        class JSONString;

        class JSONValue : public virtual ReferenceCounter,
                          public virtual AbstractElement
        {
        private:
            AbstractType type;

        protected:
            __declspec(dllexport) JSONValue(AbstractType type);

        public:
            __declspec(dllexport) JSONValue();
            __declspec(dllexport) virtual ~JSONValue();
            __declspec(dllexport) AbstractType getType() const override;
            __declspec(dllexport) virtual Text toString() const override;
            __declspec(dllexport) virtual JSONValue* clone() const;
            __declspec(dllexport) JSONBool* asBool() const;
            __declspec(dllexport) JSONNumber* asNumber() const;
            __declspec(dllexport) JSONString* asString() const;
            __declspec(dllexport) JSONArray* asArray() const;
            __declspec(dllexport) JSONObject* asObject() const;
            __declspec(
                dllexport) const AbstractBool* asAbstractBool() const override;
            __declspec(dllexport) const
                AbstractNumber* asAbstractNumber() const override;
            __declspec(dllexport) const
                AbstractString* asAbstractString() const override;
            __declspec(dllexport) const
                AbstractArray* asAbstractArray() const override;
            __declspec(dllexport) const
                AbstractObject* asAbstractObject() const override;
        };

#pragma warning(push)
#pragma warning(disable : 4250)

        class JSONBool : public AbstractBool,
                         public JSONValue

        {
        private:
            bool b;

        public:
            __declspec(dllexport) JSONBool(bool b);

            __declspec(dllexport) bool getBool() const override;
            __declspec(dllexport) Text toString() const override;
            __declspec(dllexport) JSONValue* clone() const override;
        };

        class JSONNumber : public JSONValue,
                           public AbstractNumber
        {
        private:
            double number;

        public:
            __declspec(dllexport) JSONNumber(double num);

            __declspec(dllexport) double getNumber() const override;
            __declspec(dllexport) Text toString() const override;
            __declspec(dllexport) JSONValue* clone() const override;
        };

        class JSONString : public JSONValue,
                           public AbstractString
        {
        private:
            Text string;

        public:
            __declspec(dllexport) JSONString(Text string);

            __declspec(dllexport) Text getString() const override;
            __declspec(dllexport) Text toString() const override;
            __declspec(dllexport) JSONValue* clone() const override;
        };

        class JSONArray : public JSONValue,
                          public AbstractArray
        {
        private:
            RCArray<JSONValue>* array;

        public:
            __declspec(dllexport) JSONArray();
            __declspec(dllexport) JSONArray(Text string);
            __declspec(dllexport) JSONArray(const JSONArray& arr);
            __declspec(dllexport) ~JSONArray();

            __declspec(dllexport) JSONArray& operator=(const JSONArray& arr);

            __declspec(dllexport) void addValue(JSONValue* value);
            __declspec(dllexport) void setValue(int i, JSONValue* value);
            __declspec(dllexport) void removeValue(int i);
            __declspec(dllexport) JSONValue* getValue(int i) const;
            __declspec(dllexport) JSONValue* zValue(int i) const;
            __declspec(dllexport) AbstractElement* zAbstractValue(
                int i) const override;
            __declspec(dllexport) int getLength() const override;
            __declspec(dllexport) bool isValueOfType(
                int i, AbstractType type) const;

            //! Gibt einen Iterator zur�ck.
            //! Mit ++ kann durch die Liste iteriert werden
            __declspec(dllexport) ArrayIterator<JSONValue*> begin() const;
            __declspec(dllexport) ArrayIterator<JSONValue*> end() const;

            template<class T>
            RCArray<T>* toRCArray(std::function<T*(JSONValue&)> map) const
            {
                return toRCArray([](JSONValue& v) { return 1; }, map);
            }

            template<class T>
            RCArray<T>* toRCArray(std::function<bool(JSONValue&)> filter,
                std::function<T*(JSONValue&)> map) const
            {
                RCArray<T>* result = new RCArray<T>();
                for (auto v : *array)
                {
                    if (filter(*v))
                    {
                        result->add(map(*v));
                    }
                }
                return result;
            }

            template<class T>
            Array<T>* toArray(std::function<T(JSONValue&)> map) const
            {
                return toArray([](JSONValue& v) { return 1; }, map);
                ;
            }

            template<class T>
            Array<T>* toArray(std::function<bool(JSONValue&)> filter,
                std::function<T(JSONValue&)> map) const
            {
                Array<T>* result = new Array<T>();
                for (auto v : *array)
                {
                    if (filter(*v))
                    {
                        result->add(map(*v));
                    }
                }
                return result;
            }

            __declspec(dllexport) Text toString() const override;
            __declspec(dllexport) JSONValue* clone() const override;

            template<class T> static JSONArray* fromRCArray(
                Framework::RCArray<T>& arr, std::function<JSONValue*(T&)> map)
            {
                JSONArray* array = new JSONArray();
                for (T* v : arr)
                {
                    array->addValue(map(*v));
                }
                return array;
            }

            template<class T> static JSONArray* fromArray(
                Framework::Array<T>& arr, std::function<JSONValue*(T)> map)
            {
                JSONArray* array = new JSONArray();
                for (T v : arr)
                {
                    array->addValue(map(v));
                }
                return array;
            }
        };

        class JSONObject : public JSONValue,
                           public AbstractObject
        {
        private:
            Array<Text>* fields;
            RCArray<JSONValue>* values;

        public:
            __declspec(dllexport) JSONObject();
            __declspec(dllexport) JSONObject(Text string);
            __declspec(dllexport) JSONObject(const JSONObject& obj);
            __declspec(dllexport) ~JSONObject();

            __declspec(dllexport) JSONObject& operator=(const JSONObject& obj);

            __declspec(dllexport) bool addValue(Text field, JSONValue* value);
            __declspec(dllexport) bool removeValue(Text field);
            __declspec(dllexport) bool hasValue(Text field) const override;
            __declspec(dllexport) JSONValue* getValue(Text field) const;
            __declspec(dllexport) JSONValue* zValue(Text field) const;
            __declspec(dllexport) AbstractElement* zAbstractValue(
                Text field) const override;
            __declspec(dllexport) ArrayIterator<Text> getFields();
            __declspec(dllexport) ArrayIterator<JSONValue*> getValues();
            __declspec(dllexport) Text getFieldKey(int i) const override;
            __declspec(dllexport) AbstractElement* zAbstractValue(
                int i) const override;
            __declspec(dllexport) int getFieldCount() const override;
            __declspec(dllexport) bool isValueOfType(
                Text field, AbstractType type) const;

            template<class T> T* parseTo(T* initialState,
                std::function<void(
                    T* obj, Text fieldName, JSONValue& fieldValue)> parser)
                const
            {
                auto fieldsI = fields->begin();
                auto valuesI = values->begin();
                while (fieldsI && valuesI)
                {
                    parser(initialState, fieldsI, *(JSONValue*)valuesI);
                    fieldsI++;
                    valuesI++;
                }
                return initialState;
            }

            __declspec(dllexport) Text toString() const override;
            __declspec(dllexport) JSONValue* clone() const override;
        };

#pragma warning(pop)

        __declspec(dllexport) JSONValue* loadJSONFromFile(Text path);

        namespace Parser
        {
            __declspec(dllexport) int findObjectEndInArray(const char* str);
            __declspec(dllexport) Text removeWhitespace(const char* str);
            __declspec(dllexport) JSONValue* getValue(const char* str);
            __declspec(dllexport) int findFieldEndInObject(const char* str);
            __declspec(dllexport) int findValueEndInObject(const char* str);
        }; // namespace Parser
    }      // namespace JSON
} // namespace Framework