#pragma once

#include <psemek/ecs/container.hpp>

#include <psemek/util/function.hpp>

#include <vector>

namespace psemek::ecs
{

	struct system_set
	{
		/** Add a function to this system set.
		 *
		 *  When `system_set::apply()` is called, this function is applied to
		 *    the container by calling `container.apply<Components...>(function)`.
		 *
		 *  The system set automatically manages the query cache for this function.
		 *
		 *  @tparam Components The component types, as in `container::apply`
		 *  @param function The function to be applied to a container
		 */
		template <typename ... Components, typename Function>
		void add(Function && function);

		/** Add a batch function to this system set.
		 *
		 *  When `system_set::apply()` is called, this function is applied to
		 *    the container by calling `container.batch_apply<Components...>(function)`.
		 *
		 *  The system set automatically manages the query cache for this function.
		 *
		 *  @tparam Components The component types, as in `container::apply`
		 *  @param function The function to be applied to a container
		 */
		template <typename ... Components, typename Function>
		void add_batch(Function && function);

		/** Apply this system set to a container.
		 *
		 *  The functions added to the set via `add` or `add_batch` are applied
		 *    to the container in the order in which they were added.
		 *
		 *  @param container The container to apply the system set to
		 */
		void apply(container & container);

		/** Clear this system set, removing all previously added functions.
		 */
		void clear();

	private:

		struct system_data
		{
			util::function<query_cache(container &, query_cache)> apply;
			query_cache cache = nullptr;
		};

		std::vector<system_data> systems_;
	};

	template <typename ... Components, typename Function>
	void system_set::add(Function && function)
	{
		systems_.push_back({[function = std::move(function)](container & container, query_cache cache) -> query_cache {
			return container.apply<Components...>(function, cache);
		}});
	}

	template <typename ... Components, typename Function>
	void system_set::add_batch(Function && function)
	{
		systems_.push_back({[function = std::move(function)](container & container, query_cache cache) -> query_cache {
			return container.batch_apply<Components...>(function, cache);
		}});
	}

	inline void system_set::apply(container & container)
	{
		for (auto & system : systems_)
			system.cache = system.apply(container, system.cache);
	}

	inline void system_set::clear()
	{
		systems_.clear();
	}


}
