#pragma once

#include <psemek/ecs/detail/entity_list.hpp>
#include <psemek/ecs/detail/table_container.hpp>
#include <psemek/ecs/detail/query_cache_container.hpp>
#include <psemek/ecs/detail/apply_helper.hpp>
#include <psemek/ecs/detail/all_different_types.hpp>
#include <psemek/ecs/detail/component_uuid_helper.hpp>
#include <psemek/ecs/detail/without.hpp>
#include <psemek/ecs/detail/component_registry.hpp>
#include <psemek/ecs/detail/index_container.hpp>
#include <psemek/ecs/accessor.hpp>
#include <psemek/ecs/exceptions.hpp>
#include <psemek/ecs/statistics.hpp>
#include <psemek/util/range.hpp>
#include <psemek/util/object_pool.hpp>

namespace psemek::ecs
{

	using query_cache = std::shared_ptr<detail::query_cache>;

	// TODO:
	// - Fully document which functions can be called from which callbacks
	// - Tables serialization
	// - Refactor query caches

	struct container
	{
		/** Register a component type within this container. This is used to properly deserialize
		 *    a serialized container, otherwise it wouldn't know how to create entity tables from
		 *    serialized data.
		 *
		 *  Note that creating entities or attaching components to them automatically registers all
		 *    referenced components.
		 *
		 *  @tparam Component The component type to register
		 */
		template <typename Component>
		void register_component();

		/** Create an entity with the specified components. It is faster to create an entity
		 *    with all the components at once than to create it with no components and `attach()`
		 *    them one-by-one.
		 *
		 *  After the function returns, the new entity is considered alive (`alive()` returns true).
		 *
		 *  If the entity is created during iteration (inside an `apply()` call), it is unspecified
		 *    whether the created entity will be visited by iteration or not.
		 *
		 *  This function automatically registers all the entity's component types by calling `register_component()`.
		 *
		 *  @param components The components to initialize the entity with
		 *  @return A unique handle to the created entity
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning Creating a new entity invalidates all previously created accessors
		 */
		template <typename ... Components>
		handle create(Components && ... components);

		/** Check if an entity handle refers to an alive entity, i.e. one that wasn't
		 *    destroyed yetby a `destroy()` call.
		 *
		 *  @param entity A handle to the entity
		 *  @return True if the entity is still alive, false otherwise
		 *  @pre The entity was previously obtained by a `create()` call
		 */
		bool alive(handle entity) const;

		/** Destroy an entity specified by a handle. After the call, `alive()` returns false for this handle.
		 *
		 *  If the entity is destroyed during iteration (inside an `apply()` call), the iteration
		 *    is guaranteed not to visit the destroyed entity (unless it did so before the entity
		 *    was destroyed).
		 *
		 *  @param entity A handle to the entity to be destroyed
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 */
		void destroy(handle entity);

		/** Same as destroy(), but via finally() */
		void destroy_finally(handle entity);

		/** Get an accessor for an entity, which provides access to the entity's components.
		 *  It is designed to be a single-use object; it cannot be stored as a reference to
		 *    the entity. Use handle for that instead.
		 *
		 *  @param entity A handle to the entity to be destroyed
		 *  @return An accessor to the specified entity
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 *  @warning Creating or destroying entities, as well as attaching or detaching components,
		 *    invalidates all previously created accessors
		 */
		accessor get(handle entity);

		/** Compute a string representation of an entity, based on the contained components.
		 *  A component has to implement a to_string() method in order to provide extra info.
		 *
		 *  @param entity A handle to the entity to describe
		 *  @return A string representation of an entity
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 */
		std::string describe(handle entity) const;

		/** Check if the entity can be cloned.
		 *
		 *  An entity can be cloned if all of its components are copy-constructible.
		 *
		 *  @param entity A handle to the entity to check for cloning
		 *  @return True if the entity can be cloned, false otherwise
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 */
		bool can_clone(handle entity) const;

		/** Clone the entity into a new entity by copy-constructing all components,
		 *    as if by calling `create()` with all the original entity's components.
		 *
		 *  The new entity is considered alive immediately after this call.
		 *
		 *  @param entity A handle to the entity to clone
		 *  @return A handle to the cloned entity
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 *  @throw entity_not_cloneable if the entity is not cloneable
		 */
		handle clone(handle entity);

		/** Try to clone the entity into a new entity by copy-constructing all components,
		 *    as if by calling `create()` with all the original entity's components.
		 *
		 *  The new entity is considered alive immediately after this call.
		 *
		 *  @param entity A handle to the entity to clone
		 *  @return A handle to cloned entity if the original entity was cloneable, and std::nullopt otherwise
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 */
		std::optional<handle> try_clone(handle entity);

		/** Attach new components to an existing entity, or update existing
		 *    components with new values. Other components of this entity
		 *    are left untouched.
		 *
		 *  For the purposes of `apply()` semantics, attaching behaves as if
		 *    the entity was destroyed and then recreated with the same handle.
		 *
		 *  This function automatically registers all the new component types by calling `register_component()`.
		 *
		 *  @param entity A handle to the entity to attach components to
		 *  @param components The components to attach to the entity
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning Attaching components invalidates all previously created accessors
		 */
		template <typename ... Components>
		void attach(handle entity, Components && ... components);

		/** Detach (remove) components from an existing entity. Other components of this
		 *    entity are left untouched. Detaching a component that doesn't exist is not
		 *    an error, and does nothing.
		 *
		 *  For the purposes of `apply()` semantics, detaching behaves as if
		 *    the entity was destroyed and then recreated with the same handle.
		 *
		 *  @param entity A handle to the entity to detach components from
		 *  @tparam Components The component types to detach from the entity
		 *  @pre The entity was previously obtained by a `create()` call and is `alive()`
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning Detaching components invalidates all previously created accessors
		 */
		template <typename ... Components>
		void detach(handle entity);

		/** Same as detach(), but via finally() */
		template <typename ... Components>
		void detach_finally(handle entity);

		/** Create a query cache that can be used to speed up `apply()` calls.
		 *
		 *  The constness of the component types is ignored.
		 *
		 *  The component types can be equal to ecs::without<Component>.
		 *
		 *  @tparam Components The component types matching the corresponding `apply()` call
		 *  @return A query cache
		 */
		template <typename ... Components>
		query_cache cache();

		/** Apply a function to all entities having the specified components, in unspecified order.
		 *
		 *  The function must have one of the following signatures:
		 *      void(components...)
		 *      void(handle, components...)
		 *      void(container, components...)
		 *      void(container, handle, components...)
		 *
		 *  The component types can be const-qualified, in which case the corresponding function must
		 *    also accept the corresponding components by a const reference. This can be used to indicate
		 *    that this function doesn't modify specific components, to prevent modification callbacks
		 *    from being triggered.
		 *
		 *  The component types can be equal to ecs::without<Component>, indicating that entities having
		 *    this component type will not be visited by this function. These component types are not
		 *    included in the called function signature.
		 *
		 *  The function can freely create or destroy entities, and or attach/detach
		 *    components to existing entities. It is unspecified whether the function
		 *    will or will not visit newly created entities during this `apply()` call.
		 *    The function is guaranteed not to visit destroyed entities (unless it did
		 *    so before the entity was destroyed).
		 *
		 *  An optional query cache can be supplied to speed up iteration. If the caller doesn't
		 *    supply a query cache, a new query cache is created as if by calling `cache<Components...>()`.
		 *    In any case, the (user-provided or newly-created) query cache is returned.
		 *
		 *  @param function The function to apply to all entities with the specified components
		 *  @param cache A query cache
		 *  @return The query cache, either user-provided, or newly-created
		 *  @pre The query query cache was created with the exact same sequence of component types, ignoring
		 *    constness
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning If the function accesses passed components after destroying the
		 *    currently visited entity, the behavior is undefined
		 *  @warning If the function recursively calls `apply()` or `batch_apply()`, the
		 *    behavior is undefined
		 */
		template <typename ... Components, typename Function>
		query_cache apply(Function && function, query_cache cache = {});

		/** Apply a function to all entities having the specified components,
		 *    in unspecified order. Instead of applying the function to each
		 *    entity separately, it is applied in "batch" mode, i.e. to array
		 *    views of respective components.
		 *
		 *  The function must have one of the following signatures:
		 *      void(span<components>...)
		 *      void(span<handle const>, span<components>...)
		 *      void(container, span<components>...)
		 *      void(container, span<handle const>, span<components>...)
		 *
		 *  The component types can be const-qualified, in which case the corresponding function must
		 *    also accept the corresponding components by views to const. This can be used to indicate
		 *    that this function doesn't modify specific components, to prevent modification callbacks
		 *    from being triggered.
		 *
		 *  The component types can be equal to ecs::without<Component>, indicating that entities having
		 *    this component type will not be visited by this function. These component types are not
		 *    included in the called function signature.
		 *
		 *  The sizes of all spans within a single function call are the same,
		 *    except for empty component types (i.e. std::is_empty_v<Component> is true),
		 *    each having an unspecified non-zero size. If all components are empty types,
		 *    the function should use the entity handles array view to compute the number
		 *    of visited entities.
		 *
		 *  The function can freely create or destroy entities, and or attach/detach
		 *    components to existing entities. It is unspecified whether the function
		 *    will or will not visit newly created entities during this `apply()` call.
		 *    The function is guaranteed not to visit destroyed entities (unless it did
		 *    so before the entity was destroyed).
		 *
		 *  An optional query cache can be supplied to speed up iteration. If the caller doesn't
		 *    supply a query cache, a new query cache is created as if by calling `cache<Components...>()`.
		 *    In any case, the (user-provided or newly-created) query cache is returned.
		 *
		 *  @param function The function to batch-apply to all entities with the specified components
		 *  @param cache A query cache
		 *  @return The query cache, either user-provided, or newly-created
		 *  @pre The query query cache was created with the exact same sequence of component types, ignoring
		 *    constness
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning If the function recursively calls `apply()` or `batch_apply()`, the behavior is undefined
		 */
		template <typename ... Components, typename Function>
		query_cache batch_apply(Function && function, query_cache cache = {});

		/** Register a constructor. Each time an entity is created that has
		 *    the specified set of components, the constructor is called for
		 *    this entity immediately after it is created.
		 *
		 *  The entity is considered `alive()` during the constructor invocation.
		 *
		 *  The component types can be const-qualified, in which case the corresponding function must
		 *    also accept the corresponding components by a const reference.
		 *
		 *  The component types can be equal to ecs::without<Component>, indicating that entities having
		 *    this component type will not be visited by this constructor. These component types are not
		 *    included in the called function signature.
		 *
		 *  When attaching components to an entity, the constructor is called
		 *    exactly when the entity didn't match the constructor's component types
		 *    before attaching new components, and does match them after attaching.
		 *    However, this matching check ignores the constructor's 'without' parameters.
		 *    TODO: Figure out the behavior concerning 'without' parameters
		 *
		 *  The constructor function must have the same signature as a function
		 *    passed to the `apply<Components...>()` call.
		 *
		 *  @param function A function to be applied to created entity's components
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning If the constructor modifies the entity's archetype (i.e. attaches or
		 *    detaches components), the behavior is undefined
		 *  @warning If the constructor destroys the entity, the behavior is undefined
		 */
		template <typename ... Components, typename Function>
		void constructor(Function && function);

		/** Register a destructor. Each time an entity is destroyed that has
		 *    the specified set of components, the destructor is called for
		 *    this entity immediately before it will be destroyed.
		 *
		 *  The entity is considered `alive()` during the destructor invocation.
		 *
		 *  The component types can be const-qualified, in which case the corresponding function must
		 *    also accept the corresponding components by a const reference.
		 *
		 *  The component types can be equal to ecs::without<Component>, indicating that entities having
		 *    this component type will not be visited by this destructor. These component types are not
		 *    included in the called function signature.
		 *
		 *  When detaching components from an entity, the destructor is called
		 *    exactly when the entity did match the destructor's component types
		 *    before detaching components, and doesn't match them after detaching.
		 *    However, this matching check ignores the destructors's 'without' parameters.
		 *    TODO: Figure out the behavior concerning 'without' parameters
		 *
		 *  The destructor function must have the same signature as a function
		 *    passed to the `apply<Components...>()` call.
		 *
		 *  Note that there is no way to cancel the entity's destruction.
		 *
		 *  @param function A function to be applied to a to-be destroyed entity's components
		 *  @warning If any two of the passed component types are equal, the call fails with
		 *    a compilation error
		 *  @warning If the destructor modifies the entity's archetype (i.e. attaches or
		 *    detaches components), the behavior is undefined
		 *  @warning If the destructor destroys the entity recursively, the behavior is undefined
		 */
		template <typename ... Components, typename Function>
		void destructor(Function && function);

		/** Call a callback after exiting all currently executing ECS container methods.
		 *
		 *  If no ECS container method is currently executed, call the callback immediately instead.
		 *
		 *  The function must have one of the following signatures:
		 *      void()
		 *      void(container)
		 *
		 *  This method is meant to be called from inside other callbacks (typically
		 *    constructors and destructors) and can be used to add extra modifications
		 *    to entities that don't clash with other callbacks and archetype changes.
		 *
		 *  Generally, if a constructor, destructor, apply or batch apply function
		 *    changes the archetype of some entities, it should do so using `finally`
		 *    instead of doing that directly.
		 *
		 *  @param function A callback to be called before the topmost currently executing
		 *    ECS container method returns.
		 *  @warning If a callback adds new callbacks via `finally`, those will also get executed,
		 *    which potentially leads to an infinite loop.
		 */
		template <typename Function>
		void finally(Function && function);

		template <typename Index, typename ... Args>
		Index & index(Args && ... args);

		template <typename Index, typename Factory>
		Index & index_factory(Factory && factory);

		template <typename ... Components>
		std::size_t memory_usage();

		std::size_t entity_count();

		std::size_t cache_count();

		std::size_t table_count();

		struct statistics statistics();

	private:
		detail::entity_list entity_list_;
		detail::table_container table_container_;
		detail::query_cache_container query_cache_container_;
		detail::component_registry component_registry_;
		detail::index_container index_container_;

		int next_constructor_id_ = 0;
		int next_destructor_id_ = 0;

		util::object_pool<std::vector<util::uuid>> uuid_list_pool_;
		util::object_pool<util::hash_set<util::uuid>> uuid_set_pool_;

		std::size_t method_recursion_depth_ = 0;
		std::vector<util::function<void(container &)>> finally_callbacks_;

		#ifdef PSEMEK_DEBUG
			util::hash_set<ecs::handle> currently_changing_archetype_;
		#endif

		detail::table * insert_table(std::vector<std::unique_ptr<detail::column>> columns);
		void do_destroy(handle entity);
		void remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities);
		void finalize_iteration(detail::table & table);
		void finalize_method();
	};

	template <typename Component>
	void container::register_component()
	{
		component_registry_.register_component<std::remove_cvref_t<Component>>();
	}

	template <typename ... Components>
	handle container::create(Components && ... components)
	{
		static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");

		++method_recursion_depth_;

		(register_component<std::remove_cvref_t<Components>>(), ...);

		detail::component_uuid_helper<std::remove_cvref_t<Components>...> uuids;

		auto table = table_container_.get(uuids.get());

		if (!table)
		{
			std::vector<std::unique_ptr<detail::column>> columns;
			(columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), ...);

			table = insert_table(std::move(columns));
		}

		if (table->get_iteration_data())
			table = table->get_delayed_table();

		auto row = table->row_count();
		auto id = entity_list_.create(table, row);
		handle handle{id, entity_list_.get_entities()[id].epoch};
		#ifdef PSEMEK_DEBUG
			currently_changing_archetype_.insert(handle);
		#endif
		[[maybe_unused]] accessor accessor = get(handle);

		table->push_row(handle);
		((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);

		table->trigger_constructors(*this, row);

		#ifdef PSEMEK_DEBUG
			currently_changing_archetype_.erase(handle);
		#endif

		finalize_method();

		--method_recursion_depth_;

		return handle;
	}

	template <typename ... Components>
	void container::attach(handle entity, Components && ... components)
	{
		static_assert(detail::all_different_types_v<std::remove_cvref_t<Components>...>, "all component types must be different");

		#ifdef PSEMEK_DEBUG
			assert(!currently_changing_archetype_.contains(entity));
			currently_changing_archetype_.insert(entity);
		#endif

		++method_recursion_depth_;

		(register_component<Components>(), ...);

		auto uuids = uuid_list_pool_.get();
		auto attached_uuid_set = uuid_set_pool_.get();

		auto * data = entity_list_.get_entities().begin() + entity.id;
		for (auto const & column : data->table->columns())
			uuids.push_back(column->uuid());

		bool archetype_changed = false;
		((data->table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (archetype_changed = true, attached_uuid_set.insert(std::remove_cvref_t<Components>::uuid()), 0)), ...);

		for (auto const & uuid : attached_uuid_set)
			uuids.push_back(uuid);

		if (archetype_changed)
			data->table->trigger_destructors(*this, data->row, attached_uuid_set, {});

		auto table = table_container_.get(uuids);

		if (!table)
		{
			std::vector<std::unique_ptr<detail::column>> columns;
			for (auto const & column : data->table->columns())
				columns.push_back(column->clone());

			((data->table->column(std::remove_cvref_t<Components>::uuid()) ? 0 : (columns.push_back(std::make_unique<detail::column_impl<std::remove_cvref_t<Components>>>()), 0)), ...);

			table = insert_table(std::move(columns));
		}

		if (table != data->table)
		{
			if (table->get_iteration_data())
				table = table->get_delayed_table();

			auto new_row = table->move_row(entity, data->table, data->row);
			do_destroy(entity);

			data->table = table;
			data->row = new_row;
		}

		auto accessor = get(entity);
		((accessor.get<std::remove_cvref_t<Components>>() = std::forward<Components>(components)), ...);

		if (archetype_changed)
			table->trigger_constructors(*this, data->row, attached_uuid_set, {});

		attached_uuid_set.clear();
		uuids.clear();

		uuid_set_pool_.put(std::move(attached_uuid_set));
		uuid_list_pool_.put(std::move(uuids));

		#ifdef PSEMEK_DEBUG
			currently_changing_archetype_.erase(entity);
		#endif

		finalize_method();

		--method_recursion_depth_;
	}

	template <typename ... Components>
	void container::detach(handle entity)
	{
		static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");

		#ifdef PSEMEK_DEBUG
			assert(!currently_changing_archetype_.contains(entity));
			currently_changing_archetype_.insert(entity);
		#endif

		++method_recursion_depth_;

		auto detached_uuid_set = uuid_set_pool_.get();
		(detached_uuid_set.insert(std::remove_const_t<Components>::uuid()), ...);

		auto uuids = uuid_list_pool_.get();

		auto * data = entity_list_.get_entities().begin() + entity.id;
		for (auto const & column : data->table->columns())
		{
			auto const column_uuid = column->uuid();
			if (!detached_uuid_set.contains(column_uuid))
				uuids.push_back(column_uuid);
		}

		bool const archetype_changed = sizeof...(Components) > 0;

		auto table = table_container_.get(uuids);

		if (archetype_changed)
			data->table->trigger_destructors(*this, data->row, {}, detached_uuid_set);

		// Destructors could lead to reallocation of entity list
		data = entity_list_.get_entities().begin() + entity.id;

		if (!table)
		{
			std::vector<std::unique_ptr<detail::column>> columns;
			for (auto const & column : data->table->columns())
				if (!detached_uuid_set.contains(column->uuid()))
					columns.push_back(column->clone());

			table = insert_table(std::move(columns));
		}

		if (table != data->table)
		{
			if (table->get_iteration_data())
				table = table->get_delayed_table();

			auto new_row = table->move_row(entity, data->table, data->row);
			do_destroy(entity);

			data->table = table;
			data->row = new_row;
		}

		if (archetype_changed)
			table->trigger_constructors(*this, data->row, {}, detached_uuid_set);

		detached_uuid_set.clear();
		uuids.clear();
		detached_uuid_set.clear();

		uuid_list_pool_.put(std::move(uuids));
		uuid_set_pool_.put(std::move(detached_uuid_set));

		#ifdef PSEMEK_DEBUG
			currently_changing_archetype_.erase(entity);
		#endif

		finalize_method();

		--method_recursion_depth_;
	}

	template <typename ... Components>
	void container::detach_finally(handle entity)
	{
		finally([entity](container & world){
			if (world.alive(entity))
				world.detach<Components...>(entity);
		});
	}

	template <typename ... Components>
	query_cache container::cache()
	{
		typename detail::filter_with   <detail::component_uuid_helper, std::tuple<std::remove_cvref_t<Components>...>>::type with_uuids;
		typename detail::filter_without<detail::component_uuid_helper, std::tuple<std::remove_cvref_t<Components>...>>::type without_uuids;

		return query_cache_container_.create(with_uuids.get(), without_uuids.get(), table_container_);
	}

	template <typename ... Components, typename Function>
	query_cache container::apply(Function && function, query_cache cache)
	{
		static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");

		++method_recursion_depth_;

		using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components...>, Function>::type;

		static_assert(invocable_type::value, "function is not invocable with these components");

		if (!cache)
			cache = this->cache<Components...>();

		for (auto const & entry : cache->entries)
		{
			auto & iteration_data = entry.table->get_iteration_data();
			iteration_data.emplace();
			typename detail::filter_with<detail::static_apply_helper, std::tuple<Components...>>::type apply_helper(*this, entry.table->entity_handles());

			for (std::size_t i = 0; i < cache->with_uuids.size(); ++i)
				apply_helper.pointers[i] = entry.table->column(cache->with_uuids[i])->data();

			for (std::size_t i = 0; i < entry.table->row_count(); ++i)
			{
				iteration_data->current_row = i;
				apply_helper.apply(function);
				apply_helper.advance();
			}

			iteration_data.reset();
			finalize_iteration(*entry.table);
		}

		finalize_method();

		--method_recursion_depth_;

		return cache;
	}

	template <typename ... Components, typename Function>
	query_cache container::batch_apply(Function && function, query_cache cache)
	{
		static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");

		++method_recursion_depth_;

		using invocable_type = typename detail::filter_with<detail::batch_invocable, std::tuple<Components...>, Function>::type;

		static_assert(invocable_type::value, "function is not batch-invocable with these components");

		if (!cache)
			cache = this->cache<Components...>();

		for (auto const & entry : cache->entries)
		{
			auto & iteration_data = entry.table->get_iteration_data();
			iteration_data.emplace();
			iteration_data->current_row = entry.table->row_count();

			typename detail::filter_with<detail::static_apply_helper, std::tuple<Components...>>::type apply_helper(*this, entry.table->entity_handles());

			for (std::size_t i = 0; i < cache->with_uuids.size(); ++i)
				apply_helper.pointers[i] = entry.table->column(cache->with_uuids[i])->data();

			apply_helper.batch_apply(function);

			iteration_data.reset();
			finalize_iteration(*entry.table);
		}

		finalize_method();

		--method_recursion_depth_;

		return cache;
	}

	template <typename ... Components, typename Function>
	void container::constructor(Function && function)
	{
		static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");

		using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components ...>, Function>::type;

		static_assert(invocable_type::value, "function is not invocable with these components");

		auto id = next_constructor_id_++;

		auto constructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
			return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components, bool force){

				bool const invoke = force
					|| (detail::contains_helper<Components>::contains(attached_components) || ...)
					|| (detail::contains_helper<Components>::contains_without(detached_components) || ...)
					;
				if (!invoke)
					return;

				typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::type apply_helper(container, table.entity_handles());

				for (std::size_t i = 0; i < apply_helper.column_count; ++i)
					apply_helper.pointers[i] = table.columns()[column_indices[i]]->data();

				apply_helper.advance(row);
				apply_helper.apply(function);
			};
		};

		query_cache cache = this->cache<Components...>();

		cache->constructor_factories.push_back([id, constructor_factory](auto const & ... args){ return detail::ordered_table_callback{id, constructor_factory(args...)}; });
		for (auto const & entry : cache->entries)
			entry.table->add_constructor({id, constructor_factory(entry.columns_indices)});
	}

	template <typename ... Components, typename Function>
	void container::destructor(Function && function)
	{
		static_assert(detail::all_different_types_v<std::remove_const_t<Components>...>, "all component types must be different");

		using invocable_type = typename detail::filter_with<detail::invocable, std::tuple<Components ...>, Function>::type;

		static_assert(invocable_type::value, "function is not invocable with these components");

		auto id = next_destructor_id_++;

		auto destructor_factory = [function = std::move(function)](std::vector<std::uint32_t> const & column_indices) -> detail::table_callback {
			return [function, column_indices](container & container, detail::table & table, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components, bool force){
				bool const invoke = force
					|| (detail::contains_helper<Components>::contains_without(attached_components) || ...)
					|| (detail::contains_helper<Components>::contains(detached_components) || ...)
					;
				if (!invoke)
					return;

				typename detail::filter_with<detail::static_apply_helper, std::tuple<Components ...>>::type apply_helper(container, table.entity_handles());

				for (std::size_t i = 0; i < apply_helper.column_count; ++i)
					apply_helper.pointers[i] = table.columns()[column_indices[i]]->data();

				apply_helper.advance(row);
				apply_helper.apply(function);
			};
		};

		query_cache cache = this->cache<Components...>();

		cache->destructor_factories.push_back([id, destructor_factory](auto const & ... args){ return detail::ordered_table_callback{id, destructor_factory(args...)}; });
		for (auto const & entry : cache->entries)
			entry.table->add_destructor({id, destructor_factory(entry.columns_indices)});
	}

	template <typename Function>
	void container::finally(Function && function)
	{
		util::function<void(container &)> wrapper;
		if constexpr (std::is_invocable_v<Function, container &>)
		{
			wrapper = std::move(function);
		}
		else
		{
			wrapper = [function = std::move(function)](ecs::container &){ function(); };
		}

		if (method_recursion_depth_ == 0)
			wrapper(*this);
		else
			finally_callbacks_.push_back(std::move(wrapper));
	}

	template <typename Index, typename ... Args>
	Index & container::index(Args && ... args)
	{
		return index_container_.get<Index>(*this, std::forward<Args>(args)...);
	}

	template <typename Index, typename Factory>
	Index & container::index_factory(Factory && factory)
	{
		return index_container_.set<Index>(std::move(factory));
	}

	template <typename ... Components>
	std::size_t container::memory_usage()
	{
		auto cache = this->cache<Components...>();
		std::size_t result = 0;

		for (auto const & entry : cache->entries)
			result += entry.table->memory_usage();

		return result;
	}

}
