#pragma once

#include "Text.h"
#include "Array.h"
#include "ReferenceCounter.h"
#include <functional>

namespace Framework
{
	namespace JSON
	{
		enum class JSONType
		{
			NULL_,
			BOOLEAN,
			NUMBER,
			STRING,
			ARRAY,
			OBJECT
		};

		class JSONArray;
		class JSONObject;
		class JSONBool;
		class JSONNumber;
		class JSONString;

		class JSONValue : public virtual ReferenceCounter
		{
		private:
			JSONType type;

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

		public:
			__declspec(dllexport) JSONValue();
			__declspec(dllexport) virtual ~JSONValue();
			__declspec(dllexport) JSONType getType() const;
			__declspec(dllexport) virtual Text toString() 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;
		};

		class JSONBool : public JSONValue
		{
		private:
			bool b;

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

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

		class JSONNumber : public JSONValue
		{
		private:
			double number;

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

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

		class JSONString : public JSONValue
		{
		private:
			Text string;

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

			__declspec(dllexport) Text getString() const;
			__declspec(dllexport) Text toString() const override;
		};

		class JSONArray : public JSONValue
		{
		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) JSONValue* getValue(int i) const;
			__declspec(dllexport) int getLength() const;
			__declspec(dllexport) bool isValueOfType(int i, JSONType type) 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;


			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
		{
		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);
			__declspec(dllexport) JSONValue* getValue(Text field);
			__declspec(dllexport) Iterator< Text > getFields();
			__declspec(dllexport) Iterator< JSONValue* > getValues();
			__declspec(dllexport) int getFieldCount() const;
			__declspec(dllexport) bool isValueOfType(Text field, JSONType 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* 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);
		};
	}
}