#pragma once

#include <psemek/util/uuid.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/type_name.hpp>
#include <psemek/util/exception.hpp>
#include <psemek/util/function.hpp>

#include <memory>

namespace psemek::ecs
{

	struct container;

}

namespace psemek::ecs::detail
{

	struct index_container
	{
		template <typename Index, typename ... Args>
		Index & get(container & container, Args && ... args)
		{
			auto uuid = Index::uuid();
			if (auto it = storage_.find(uuid); it != storage_.end())
				return *reinterpret_cast<Index *>(it->second());

			if constexpr (std::is_constructible_v<Index, ecs::container &, Args && ...>)
			{
				auto ptr = std::make_unique<Index>(container, std::forward<Args>(args)...);
				auto result = ptr.get();
				storage_.insert({uuid, [ptr = std::move(ptr)]{ return ptr.get(); }});
				return *result;
			}
			else if constexpr (std::is_constructible_v<Index, Args && ...>)
			{
				auto ptr = std::make_unique<Index>(std::forward<Args>(args)...);
				auto result = ptr.get();
				storage_.insert({uuid, [ptr = std::move(ptr)]{ return ptr.get(); }});
				return *result;
			}
			else
			{
				throw util::exception("Index " + util::type_name<Index>() + " is neither present nor constructible");
			}
		}

		template <typename Index, typename Factory>
		Index & set(Factory && factory)
		{
			auto uuid = Index::uuid();
			if (storage_.contains(uuid))
				throw util::exception("Index " + util::type_name<Index>() + " is already set");

			auto result = factory();
			storage_[Index::uuid()] = std::move(factory);
			return *result;
		}

	private:
		util::hash_map<util::uuid, util::function<void*()>> storage_;
	};

}
