#pragma once

#include <psemek/ecs/detail/table.hpp>
#include <psemek/ecs/detail/entity_list.hpp>

namespace psemek::ecs
{

	struct container;

}

namespace psemek::ecs::detail
{

	template <typename Function, typename ... Components>
	struct invocable
	{
		static constexpr bool value = false
			|| std::invocable<Function, container &, handle, Components & ...>
			|| std::invocable<Function, container &, Components & ...>
			|| std::invocable<Function, handle, Components & ...>
			|| std::invocable<Function, Components & ...>
			;
	};

	template <typename Function, typename ... Components>
	void invoke(Function && function, container & parent, handle const & handle, Components & ... components)
	{
		if constexpr (std::invocable<Function, container &, ecs::handle, Components & ...>)
		{
			function(parent, handle, components...);
		}
		else if constexpr (std::invocable<Function, container &, Components & ...>)
		{
			function(parent, components...);
		}
		else if constexpr (std::invocable<Function, ecs::handle, Components & ...>)
		{
			function(handle, components...);
		}
		else
		{
			function(components...);
		}
	}

	template <typename Function, typename ... Components>
	struct batch_invocable
	{
		static constexpr bool value = false
			|| std::invocable<Function, container &, util::span<handle const>, util::span<Components> ...>
			|| std::invocable<Function, container &, util::span<Components> ...>
			|| std::invocable<Function, util::span<handle const>, util::span<Components> ...>
			|| std::invocable<Function, util::span<Components> ...>
			;
	};

	template <typename Function, typename ... Components>
	void batch_invoke(Function && function, container & parent, std::size_t count, handle const * handles, Components * ... components)
	{
		util::span<handle const> handles_span{handles, count};

		if constexpr (std::invocable<Function, container &, util::span<handle const>, util::span<Components> ...>)
		{
			function(parent, handles_span, util::span<Components>{components, is_empty_v<Components> ? 1 : count} ...);
		}
		else if constexpr (std::invocable<Function, util::span<handle const>, util::span<Components> ...>)
		{
			function(handles_span, util::span<Components>{components, is_empty_v<Components> ? 1 : count} ...);
		}
		else if constexpr (std::invocable<Function, container &, util::span<Components> ...>)
		{
			function(parent, util::span<Components>{components, is_empty_v<Components> ? 1 : count} ...);
		}
		else
		{
			function(util::span<Components>{components, is_empty_v<Components> ? 1 : count} ...);
		}
	}

	template <typename ... Components>
	struct static_apply_helper
	{
		static constexpr std::size_t column_count = sizeof...(Components);

		container & parent;
		std::size_t row_count;
		handle const * entity_handles_pointer;
		// (+1) to prevent zero-sized array
		std::uint8_t * pointers[sizeof...(Components) + 1];

		static_apply_helper(container & parent, util::span<handle const> entity_handles)
			: parent(parent)
			, row_count(entity_handles.size())
			, entity_handles_pointer(entity_handles.data())
		{}

		template <typename Function>
		void apply(Function && function)
		{
			apply_impl(function, std::make_index_sequence<sizeof...(Components)>{});
		}

		template <typename Function, std::size_t ... I>
		void apply_impl(Function && function, std::index_sequence<I...>)
		{
			invoke(function, parent, *entity_handles_pointer, *reinterpret_cast<Components *>(pointers[I]) ...);
		}

		template <typename Function>
		void batch_apply(Function && function)
		{
			batch_apply_impl(function, std::make_index_sequence<sizeof...(Components)>{});
		}

		template <typename Function, std::size_t ... I>
		void batch_apply_impl(Function && function, std::index_sequence<I...>)
		{
			batch_invoke(function, parent, row_count, entity_handles_pointer, reinterpret_cast<Components *>(pointers[I]) ...);
		}

		std::size_t size() const
		{
			return row_count;
		}

		void advance()
		{
			++entity_handles_pointer;

			std::size_t i = 0;
			((pointers[i++] += stride<Components>()), ...);
		}

		void advance(std::size_t rows)
		{
			entity_handles_pointer += rows;

			std::size_t i = 0;
			((pointers[i++] += stride<Components>() * rows), ...);
		}
	};

}
