#pragma once

#include <psemek/ecs/detail/column.hpp>
#include <psemek/util/uuid.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/function.hpp>
#include <psemek/util/exception.hpp>
#include <psemek/util/to_string.hpp>

namespace psemek::ecs::detail
{

	struct duplicate_uuid_exception
		: util::exception
	{
		duplicate_uuid_exception(util::uuid const & uuid, std::type_info const & type1, std::type_info const & type2, util::stacktrace stacktrace = {})
			: util::exception(util::to_string("Found duplicate UUID ", uuid, " for components ", util::type_name(type1), " and ", util::type_name(type2)), std::move(stacktrace))
			, uuid_(uuid)
			, type1_(type1)
			, type2_(type2)
		{}

		util::uuid const & uuid() const { return uuid_; }
		std::type_info const & type1() const { return type1_; }
		std::type_info const & type2() const { return type2_; }

	private:
		util::uuid uuid_;
		std::type_info const & type1_;
		std::type_info const & type2_;
	};

	struct component_registry
	{
		template <typename Component>
		void register_component()
		{
			auto const & uuid = Component::uuid();

			if (auto it = types_.find(uuid); it != types_.end())
			{
				if (it->second != &typeid(Component))
					throw duplicate_uuid_exception(uuid, *(it->second), typeid(Component));
				else
					return;
			}

			types_.insert({uuid, &typeid(Component)});

			column_factories_.insert({uuid, []{
				return std::make_unique<column_impl<Component>>();
			}});
		}

		std::unique_ptr<column> create_column(util::uuid const & uuid) const
		{
			if (auto it = column_factories_.find(uuid); it != column_factories_.end())
				return it->second();
			return nullptr;
		}

	private:
		util::hash_map<util::uuid, std::type_info const *> types_;
		util::hash_map<util::uuid, util::function<std::unique_ptr<column>()>> column_factories_;
	};

}
