#pragma once

#include <psemek/ecs/container.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/function.hpp>
#include <psemek/util/unused.hpp>

namespace psemek::ecs
{

	namespace detail
	{

		struct default_context_factory
		{
			int operator()() const noexcept
			{
				return 0;
			}
		};

	}

	struct dispatcher
	{
		dispatcher() = default;

		dispatcher(ecs::container & container)
			: container_(&container)
		{}

		explicit operator bool() const
		{
			return container_ != nullptr;
		}

		ecs::container & container()
		{
			return *container_;
		}

		template <typename Event, typename Handler>
		void handler(Handler handler)
		{
			handlers_[Event::uuid()].push_back([handler = std::move(handler), this](void const * event_ptr) mutable {
				auto const & event = *reinterpret_cast<Event const *>(event_ptr);

				if constexpr (std::invocable<Handler, Event const &, ecs::container &>)
				{
					handler(event, *container_);
				}
				else
				{
					unused(this);
					handler(event);
				}
			});
		}

		template <typename Event, typename ... Components, typename System, typename ContextFactory = detail::default_context_factory>
		void system(System system, ContextFactory && context_factory = ContextFactory{})
		{
			handlers_[Event::uuid()].push_back([system = std::move(system), this, cache = container_->cache<Components...>(), context_factory = std::move(context_factory)](void const * event_ptr) mutable {
				auto const & event = *reinterpret_cast<Event const *>(event_ptr);

				[[maybe_unused]] auto context = context_factory();

				container_->apply<Components...>([&]<typename ... FilteredComponents>(ecs::container & container, ecs::handle entity, FilteredComponents & ... filtered_components){
					if constexpr (std::invocable<System, Event const &, ecs::container &, handle, FilteredComponents & ...>)
					{
						system(event, container, entity, filtered_components...);
					}
					else if constexpr (std::invocable<System, Event const &, ecs::container &, FilteredComponents & ...>)
					{
						system(event, container, filtered_components...);
					}
					else if constexpr (std::invocable<System, Event const &, handle, FilteredComponents & ...>)
					{
						system(event, entity, filtered_components...);
					}
					else
					{
						system(event, filtered_components...);
					}
				});
			});
		}

		template <typename Event>
		void dispatch(Event const & event)
		{
			for (auto & handler : handlers_[event.uuid()])
				handler(&event);
		}

	private:
		ecs::container * container_;

		util::hash_map<util::uuid, std::vector<util::function<void(void const *)>>> handlers_;
	};

}
