#pragma once

#include <charconv>
#include <format>
#include <functional>
#include <glm/glm.hpp>
#include <iosfwd>
#include <map>
#include <memory>
#include <optional>
#include <span>
#include <special_members.h>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <typeinfo>
#include <utility>
#include <vector>

namespace glm {
	template<glm::length_t L, typename T, glm::qualifier Q> struct vec;
}

namespace Persistence {
	struct Selection;
	using SelectionPtr = std::unique_ptr<Selection>;
	using Stack = std::stack<SelectionPtr>;

	struct Writer {
		Writer() = default;
		virtual ~Writer() = default;
		DEFAULT_MOVE_COPY(Writer);

		virtual void beginObject() const = 0;
		virtual void beginArray() const = 0;
		virtual void pushValue(bool value) const = 0;
		virtual void pushValue(float value) const = 0;
		virtual void pushValue(int value) const = 0;
		virtual void pushValue(std::nullptr_t) const = 0;
		virtual void pushValue(std::string_view value) const = 0;
		virtual void nextValue() const = 0;
		virtual void pushKey(std::string_view k) const = 0;
		virtual void endArray() const = 0;
		virtual void endObject() const = 0;
	};

	struct Selection {
		Selection() = default;
		virtual ~Selection() = default;
		DEFAULT_MOVE_COPY(Selection);

		virtual void setValue(std::string_view);
		virtual void setValue(bool);
		virtual void setValue(std::nullptr_t);
		virtual void setValue(std::string &&);
		virtual void beginArray(Stack &);
		virtual void beginObject(Stack &);
		virtual void endObject(Stack &);
		virtual void beforeValue(Stack &);
		[[nodiscard]] virtual SelectionPtr select(const std::string &);

		[[nodiscard]] virtual bool needsWrite() const;
		virtual void write(const Writer &) const;
	};

	template<typename T> struct SelectionT;

	template<typename T> struct SelectionV : public Selection {
		explicit SelectionV(T & value) : v {value} { }

		void
		beforeValue(Stack &) override
		{
		}

		[[nodiscard]] static SelectionPtr
		make(T & value)
		{
			return make_s<SelectionT<T>>(value);
		}

		template<typename S, typename... Extra>
		[[nodiscard]] static SelectionPtr
		make_s(T & value, Extra &&... extra)
		{
			return std::make_unique<S>(value, std::forward<Extra>(extra)...);
		}

		T & v;
	};

	template<typename T>
	concept Arithmatic = std::is_arithmetic_v<T>;

	template<> struct SelectionT<bool> : public SelectionV<bool> {
		using SelectionV<bool>::SelectionV;
		using Selection::setValue;

		void
		setValue(bool evalue) override
		{
			this->v = evalue;
		}

		void
		setValue(std::string && evalue) override
		{
			using namespace std::literals;
			if (!(this->v = evalue == "true"sv)) {
				if (evalue != "false"sv) {
					throw std::runtime_error("Value conversion failure");
				}
			}
		}

		void
		write(const Writer & out) const override
		{
			out.pushValue(this->v);
		}
	};

	template<Arithmatic T> struct SelectionT<T> : public SelectionV<T> {
		using SelectionV<T>::SelectionV;
		using Selection::setValue;

		void
		setValue(std::string_view evalue) override
		{
			if (auto res = std::from_chars(evalue.data(), evalue.data() + evalue.length(), this->v).ec;
					res != std::errc {}) {
				throw std::runtime_error("Value conversion failure");
			}
		}

		void
		setValue(std::string && evalue) override
		{
			setValue(std::string_view {evalue});
		}

		void
		write(const Writer & out) const override
		{
			out.pushValue(this->v);
		}
	};

	template<typename T> struct SelectionT : public SelectionV<T> {
		using SelectionV<T>::SelectionV;
		using Selection::setValue;

		void
		setValue(T && evalue) override
		{
			std::swap(this->v, evalue);
		}

		void
		write(const Writer & out) const override
		{
			out.pushValue(this->v);
		}
	};

	template<typename T> struct SelectionT<std::optional<T>> : public SelectionT<T> {
		explicit SelectionT(std::optional<T> & value) : SelectionT<T> {value.emplace()} { }
	};

	struct Persistable;

	struct PersistenceStore {
		using SelectionFactory = std::function<SelectionPtr()>;
		PersistenceStore() = default;
		virtual ~PersistenceStore() = default;
		DEFAULT_MOVE_NO_COPY(PersistenceStore);

		template<typename T> [[nodiscard]] inline bool persistType(const T * const, const std::type_info & ti);

		enum class NameAction { Push, HandleAndContinue, Ignore };
		using NameActionSelection = std::pair<NameAction, SelectionPtr>;

		template<typename Helper, typename T>
		[[nodiscard]] inline bool
		persistValue(const std::string_view key, T & value)
		{
			auto [act, s] = setName(key, [&value]() {
				return std::make_unique<Helper>(value);
			});
			if (act != NameAction::Ignore) {
				sel = std::move(s);
				if (act == NameAction::HandleAndContinue) {
					selHandler();
				}
			}
			return (act != NameAction::Push);
		}

		[[nodiscard]] virtual NameActionSelection setName(const std::string_view key, SelectionFactory &&) = 0;
		virtual void selHandler() {};
		virtual void setType(const std::string_view, const Persistable *) = 0;

		SelectionPtr sel {};
	};

	struct PersistenceSelect : public PersistenceStore {
		explicit PersistenceSelect(const std::string & n);

		NameActionSelection setName(const std::string_view key, SelectionFactory &&) override;

		void setType(const std::string_view, const Persistable *) override;

		const std::string & name;
	};

	struct PersistenceWrite : public PersistenceStore {
		explicit PersistenceWrite(const Writer & o, bool sh);

		NameActionSelection setName(const std::string_view key, SelectionFactory &&) override;

		void selHandler() override;

		void setType(const std::string_view tn, const Persistable * p) override;

		bool first {true};
		const Writer & out;
		bool shared;
	};

	template<typename T> struct SelectionT<std::span<T>> : public SelectionV<std::span<T>> {
		using V = std::span<T>;

		struct Members : public SelectionV<V> {
			using SelectionV<V>::SelectionV;

			void
			beforeValue(Stack & stk) override
			{
				stk.push(SelectionV<T>::make(this->v[idx++]));
			}

			std::size_t idx {0};

			void
			write(const Writer & out) const override
			{
				for (std::size_t n = 0; n < this->v.size(); n += 1) {
					if (n) {
						out.nextValue();
					}
					SelectionT<T> {this->v[n]}.write(out);
				}
			}
		};

		using SelectionV<V>::SelectionV;
		using SelectionV<V>::setValue;

		void
		setValue(std::string && s) override
		{
			std::stringstream ss {std::move(s)};
			for (std::size_t n = 0; n < this->v.size(); n += 1) {
				ss >> this->v[n];
				ss.get();
			}
		}

		void
		beginArray(Stack & stk) override
		{
			stk.push(this->template make_s<Members>(this->v));
		}

		void
		write(const Writer & out) const override
		{
			out.beginArray();
			Members {this->v}.write(out);
			out.endArray();
		}
	};

	template<glm::length_t L, typename T, glm::qualifier Q>
	struct SelectionT<glm::vec<L, T, Q>> : public SelectionT<std::span<T>> {
		SelectionT(glm::vec<L, T, Q> & v) : SelectionT<std::span<T>> {spn}, spn {&v[0], L} { }

		std::span<T> spn;
	};

	template<typename T> struct SelectionT<std::vector<T>> : public SelectionV<std::vector<T>> {
		using V = std::vector<T>;

		struct Members : public SelectionV<V> {
			using SelectionV<V>::SelectionV;

			void
			beforeValue(Stack & stk) override
			{
				stk.push(SelectionV<T>::make(this->v.emplace_back()));
			}

			void
			write(const Writer & out) const override
			{
				for (auto & member : this->v) {
					if (&member != &this->v.front()) {
						out.nextValue();
					}
					SelectionT<T> {member}.write(out);
				}
			}
		};

		using SelectionV<V>::SelectionV;

		void
		beginArray(Stack & stk) override
		{
			stk.push(this->template make_s<Members>(this->v));
		}

		void
		write(const Writer & out) const override
		{
			out.beginArray();
			Members {this->v}.write(out);
			out.endArray();
		}
	};

	template<typename... T> struct SelectionT<std::tuple<T...>> : public SelectionV<std::tuple<T...>> {
		using V = std::tuple<T...>;
		using SelectionV<V>::SelectionV;

		struct Members : public SelectionV<V> {
			template<size_t... Idx>
			explicit Members(V & v, std::integer_sequence<size_t, Idx...>) :
				SelectionV<V> {v}, members {SelectionV<std::tuple_element_t<Idx, V>>::make(std::get<Idx>(v))...}
			{
			}

			void
			beforeValue(Stack & stk) override
			{
				stk.push(std::move(members[idx++]));
			}

			std::size_t idx {0};
			std::array<SelectionPtr, std::tuple_size_v<V>> members;
		};

		void
		beginArray(Stack & stk) override
		{
			stk.push(this->template make_s<Members>(
					this->v, std::make_integer_sequence<size_t, std::tuple_size_v<V>>()));
		}
	};

	template<typename T, typename U> struct SelectionT<std::pair<T, U>> : public SelectionV<std::pair<T, U>> {
		using V = std::pair<T, U>;
		using SelectionV<V>::SelectionV;

		struct Members : public SelectionV<V> {
			explicit Members(V & v) :
				SelectionV<V> {v}, members {
										   SelectionV<T>::make(v.first),
										   SelectionV<U>::make(v.second),
								   }
			{
			}

			void
			beforeValue(Stack & stk) override
			{
				stk.push(std::move(members[idx++]));
			}

			std::size_t idx {0};
			std::array<SelectionPtr, 2> members;
		};

		void
		beginArray(Stack & stk) override
		{
			stk.push(this->template make_s<Members>(this->v));
		}
	};

	template<typename Map, typename Type = typename Map::mapped_type, auto Key = &Type::element_type::id>
	struct MapByMember : public Persistence::SelectionT<Type> {
		MapByMember(Map & m) : Persistence::SelectionT<Type> {s}, map {m} { }

		using Persistence::SelectionT<Type>::SelectionT;

		void
		endObject(Persistence::Stack & stk) override
		{
			// TODO test with unique_ptr
			map.emplace(std::invoke(Key, s), std::move(s));
			Persistence::SelectionT<Type>::endObject(stk);
		}

	private:
		Type s;
		Map & map;
	};

	template<typename Container, typename Type = typename Container::value_type>
	struct Appender : public Persistence::SelectionT<Type> {
		Appender(Container & c) : Persistence::SelectionT<Type> {s}, container {c} { }

		using Persistence::SelectionT<Type>::SelectionT;

		void
		endObject(Persistence::Stack & stk) override
		{
			// TODO test with unique_ptr
			container.emplace_back(std::move(s));
			Persistence::SelectionT<Type>::endObject(stk);
		}

	private:
		Type s;
		Container & container;
	};

	struct Persistable {
		Persistable() = default;
		virtual ~Persistable() = default;
		DEFAULT_MOVE_COPY(Persistable);

		virtual bool persist(PersistenceStore & store) = 0;
		virtual void postLoad();

		[[nodiscard]] virtual std::string getId() const;

		template<typename T>
		[[nodiscard]] constexpr static auto
		typeName()
		{
			constexpr std::string_view name {__PRETTY_FUNCTION__};
			constexpr auto s {name.find("T = ") + 4}, e {name.rfind(']')};
			return name.substr(s, e - s);
		}

		template<typename T> static void addFactory() __attribute__((constructor));
		static void addFactory(const std::string_view, std::function<std::unique_ptr<Persistable>()>,
				std::function<std::shared_ptr<Persistable>()>);
		[[nodiscard]] static std::unique_ptr<Persistable> callFactory(const std::string_view);
		[[nodiscard]] static std::shared_ptr<Persistable> callSharedFactory(const std::string_view);
	};

	template<typename T>
	void
	Persistable::addFactory()
	{
		addFactory(typeName<T>(), std::make_unique<T>, std::make_shared<T>);
	}

	template<typename T>
	inline bool
	PersistenceStore::persistType(const T * const v, const std::type_info & ti)
	{
		if constexpr (!std::is_abstract_v<T>) {
			[[maybe_unused]] constexpr auto f = &Persistable::addFactory<T>;
		}
		if (typeid(std::decay_t<T>) == ti) {
			setType(Persistable::typeName<T>(), v);
		}
		return true;
	}

	class ParseBase {
	public:
		using SharedObjects = std::map<std::string, std::shared_ptr<Persistable>>;
		using SharedObjectsWPtr = std::weak_ptr<SharedObjects>;
		using SharedObjectsPtr = std::shared_ptr<SharedObjects>;

		ParseBase();
		DEFAULT_MOVE_NO_COPY(ParseBase);

		template<typename T>
		static auto
		getShared(auto && k)
		{
			return std::dynamic_pointer_cast<T>(Persistence::ParseBase::sharedObjects.lock()->at(k));
		}

		template<typename... T>
		static auto
		emplaceShared(T &&... v)
		{
			return sharedObjects.lock()->emplace(std::forward<T>(v)...);
		}

	protected:
		Stack stk;

	private:
		inline static thread_local SharedObjectsWPtr sharedObjects;
		SharedObjectsPtr sharedObjectsInstance;
	};

	// TODO Move these
	using SeenSharedObjects = std::map<void *, std::string>;
	inline SeenSharedObjects seenSharedObjects;

	template<typename Ptr> struct SelectionPtrBase : public SelectionV<Ptr> {
		static constexpr auto shared = std::is_copy_assignable_v<Ptr>;
		using T = typename Ptr::element_type;

		struct SelectionObj : public SelectionV<Ptr> {
			struct MakeObjectByTypeName : public SelectionV<Ptr> {
				using SelectionV<Ptr>::SelectionV;
				using Selection::setValue;

				void
				setValue(std::string && type) override
				{
					if constexpr (shared) {
						auto no = Persistable::callSharedFactory(type);
						if (auto tno = std::dynamic_pointer_cast<T>(no)) {
							this->v = std::move(tno);
							return;
						}
					}
					else {
						auto no = Persistable::callFactory(type);
						if (dynamic_cast<T *>(no.get())) {
							this->v.reset(static_cast<T *>(no.release()));
							return;
						}
					}
					throw std::runtime_error("Named type doesn't cast to target type");
				}
			};

			struct RememberObjectById : public SelectionV<Ptr> {
				using SelectionV<Ptr>::SelectionV;
				using Selection::setValue;

				void
				setValue(std::string && id) override
				{
					ParseBase::emplaceShared(id, this->v);
				}
			};

			using SelectionV<Ptr>::SelectionV;

			[[nodiscard]] SelectionPtr
			select(const std::string & mbr) override
			{
				using namespace std::literals;
				if (mbr == "p.typeid"sv) {
					if (this->v) {
						throw std::runtime_error("cannot set object type after creation");
					}
					return this->template make_s<MakeObjectByTypeName>(this->v);
				}
				if constexpr (shared) {
					if (mbr == "p.id"sv) {
						make_default_as_needed(this->v);
						return this->template make_s<RememberObjectById>(this->v);
					}
				}
				make_default_as_needed(this->v);
				PersistenceSelect ps {mbr};
				if (this->v->persist(ps)) {
					throw std::runtime_error {std::format("Cannot find member: {}", mbr)};
				}
				return std::move(ps.sel);
			}

			void
			endObject(Stack & stk) override
			{
				make_default_as_needed(this->v);
				if (this->v) {
					this->v->postLoad();
				}
				stk.pop();
			}

			void
			write(const Writer & out) const override
			{
				out.beginObject();
				PersistenceWrite pw {out, shared};
				this->v->persist(pw);
				out.endObject();
			}
		};

		static inline void
		make_default_as_needed(Ptr & v)
		{
			if (!v) {
				if constexpr (std::is_abstract_v<T>) {
					throw std::runtime_error("cannot select member of null abstract object");
				}
				else if constexpr (shared) {
					v = std::make_shared<T>();
				}
				else {
					v = std::make_unique<T>();
				}
			}
		}

		using SelectionV<Ptr>::SelectionV;
		using Selection::setValue;

		void
		setValue(std::nullptr_t) override
		{
			this->v.reset();
		}

		void
		beginObject(Stack & stk) override
		{
			stk.push(this->template make_s<SelectionObj>(this->v));
		}

		void
		endObject(Stack & stk) override
		{
			stk.pop();
		}

		[[nodiscard]] bool
		needsWrite() const override
		{
			return this->v != nullptr;
		}

		void
		write(const Writer & out) const override
		{
			if (this->v) {
				if constexpr (shared) {
					if (const auto existing = seenSharedObjects.find(std::to_address(this->v));
							existing != seenSharedObjects.end()) {
						out.pushValue(existing->second);
						return;
					}
					seenSharedObjects.emplace(std::to_address(this->v), this->v->getId());
				}
				SelectionObj {this->v}.write(out);
			}
			else {
				out.pushValue(nullptr);
			}
		}
	};

	template<typename T> struct SelectionT<std::unique_ptr<T>> : public SelectionPtrBase<std::unique_ptr<T>> {
		using SelectionPtrBase<std::unique_ptr<T>>::SelectionPtrBase;
	};

	template<typename T> struct SelectionT<std::shared_ptr<T>> : public SelectionPtrBase<std::shared_ptr<T>> {
		using SelectionPtrBase<std::shared_ptr<T>>::SelectionPtrBase;
		using SelectionPtrBase<std::shared_ptr<T>>::setValue;

		void
		setValue(std::string && id) override
		{
			if (auto teo = ParseBase::getShared<T>(id)) {
				this->v = std::move(teo);
			}
			else {
				throw std::runtime_error("Named type doesn't cast to target type");
			}
		}
	};
}

#define STORE_TYPE store.persistType(this, typeid(*this))
#define STORE_MEMBER(mbr) STORE_NAME_MEMBER(#mbr, mbr)
#define STORE_NAME_MEMBER(name, mbr) store.persistValue<Persistence::SelectionT<decltype(mbr)>>(name, mbr)
#define STORE_HELPER(mbr, Helper) STORE_NAME_HELPER(#mbr, mbr, Helper)
#define STORE_NAME_HELPER(name, mbr, Helper) store.persistValue<Helper>(name, mbr)