#pragma once

#include <psemek/ecs/without.hpp>

#include <tuple>

namespace psemek::ecs::detail
{

	template <template <typename ...> typename MetaFunction, typename ExtraArgsTuple, typename FilteredComponentsTuple, typename ... Components>
	struct filter_with_impl;

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents>
	struct filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>>
	{
		using type = MetaFunction<ExtraArgs..., FilteredComponents...>;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, Component, RemainingComponents...>
	{
		using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, ecs::without<Component>, RemainingComponents...>
	{
		using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, ecs::without<Component> const, RemainingComponents...>
	{
		using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ExtraArgsTuple, typename FilteredComponentsTuple, typename ... Components>
	struct filter_without_impl;

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents>
	struct filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>>
	{
		using type = MetaFunction<ExtraArgs..., FilteredComponents...>;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, Component, RemainingComponents...>
	{
		using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, ecs::without<Component>, RemainingComponents...>
	{
		using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ... ExtraArgs, typename ... FilteredComponents, typename Component, typename ... RemainingComponents>
	struct filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents...>, ecs::without<Component> const, RemainingComponents...>
	{
		using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<FilteredComponents..., Component>, RemainingComponents...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ComponentsTuple, typename ... ExtraArgs>
	struct filter_with;

	template <template <typename ...> typename MetaFunction, typename ComponentsTuple, typename ... ExtraArgs>
	struct filter_without;

	template <template <typename ...> typename MetaFunction, typename ... Components, typename ... ExtraArgs>
	struct filter_with<MetaFunction, std::tuple<Components...>, ExtraArgs...>
	{
		using type = typename filter_with_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<>, Components...>::type;
	};

	template <template <typename ...> typename MetaFunction, typename ... Components, typename ... ExtraArgs>
	struct filter_without<MetaFunction, std::tuple<Components...>, ExtraArgs...>
	{
		using type = typename filter_without_impl<MetaFunction, std::tuple<ExtraArgs...>, std::tuple<>, Components...>::type;
	};

	template <typename Component>
	struct contains_helper
	{
		template <typename Container>
		static bool contains(Container const & container)
		{
			return container.contains(Component::uuid());
		}

		template <typename Container>
		static bool contains_without(Container const &)
		{
			return false;
		}
	};

	template <typename Component>
	struct contains_helper<without<Component>>
	{
		template <typename Container>
		static bool contains(Container const &)
		{
			return false;
		}

		template <typename Container>
		static bool contains_without(Container const & container)
		{
			return container.contains(Component::uuid());
		}
	};

}
