/*
 * Copyright 2015 - 2022 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/hashmap.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>

#include "ox/std/fmt.hpp"
#include "typenamecatcher.hpp"
#include "desctypes.hpp"

namespace ox {

class TypeStore {
	private:
		HashMap<String, UniquePtr<DescriptorType>> m_cache;

		static constexpr auto buildTypeId(const auto &name, auto version) noexcept {
			return ox::sfmt("{};{}", name, version);
		}

	public:
		constexpr TypeStore() noexcept = default;

		constexpr virtual ~TypeStore() noexcept = default;

		constexpr Result<const DescriptorType*> get(const auto &name, int typeVersion) const noexcept {
			const auto typeId = buildTypeId(name, typeVersion);
			oxRequire(out, m_cache.at(typeId));
			return out->get();
		}

		template<typename T>
		constexpr Result<const DescriptorType*> get() const noexcept {
			constexpr auto typeName = requireModelTypeName<T>();
			constexpr auto typeVersion = requireModelTypeVersion<T>();
			const auto typeId = buildTypeId(typeName, typeVersion);
			oxRequire(out, m_cache.at(typeId));
			return out->get();
		}

		constexpr DescriptorType *getInit(const auto &typeName, int typeVersion) noexcept {
			const auto typeId = buildTypeId(typeName, typeVersion);
			auto &out = m_cache[typeId];
			out = ox::make_unique<DescriptorType>(typeName, typeVersion);
			return out.get();
		}

		constexpr Result<const DescriptorType*> getLoad(const auto &typeName, auto typeVersion) noexcept {
			const auto typeId = buildTypeId(typeName, typeVersion);
			auto [val, err] = m_cache.at(typeId);
			if (err) {
				if (!std::is_constant_evaluated()) {
					oxRequireM(dt, loadDescriptor(typeName, typeVersion));
					for (auto &f : dt->fieldList) {
						oxReturnError(this->getLoad(f.typeName, f.typeVersion).moveTo(&f.type));
					}
					auto &out = m_cache[typeId];
					out = std::move(dt);
					return out.get();
				} else {
					return OxError(1, "Type not available");
				}
			}
			return val->get();
		}

		template<typename T>
		constexpr Result<const DescriptorType*> getLoad() noexcept {
			constexpr auto typeName = requireModelTypeName<T>();
			constexpr auto typeVersion = requireModelTypeVersion<T>();
			return getLoad(typeName, typeVersion);
		}

		constexpr void set(const auto &typeName, auto typeVersion, UniquePtr<DescriptorType> dt) noexcept {
			const auto typeId = buildTypeId(typeName, typeVersion);
			m_cache[typeId] = std::move(dt);
		}

		constexpr void set(const auto &typeName, auto typeVersion, DescriptorType *dt) noexcept {
			const auto typeId = buildTypeId(typeName, typeVersion);
			 m_cache[typeId] = UniquePtr<DescriptorType>(dt);
		}

		[[nodiscard]]
		constexpr auto typeList() const noexcept {
			auto keys = m_cache.keys();
			ox::Vector<DescriptorType*> descs;
			for (const auto &k : keys) {
				descs.emplace_back(m_cache.at(k).value->get());
			}
			return descs;
		}

	protected:
		virtual Result<UniquePtr<DescriptorType>> loadDescriptor(const ox::String&, int) noexcept {
			return OxError(1);
		}

};

}
