#pragma once

#include <psemek/ecs/detail/unordered_component_hash.hpp>
#include <psemek/ecs/detail/table.hpp>
#include <psemek/util/hash_table.hpp>

namespace psemek::ecs::detail
{

	struct table_hashset_hash
	{
		std::uint64_t operator()(util::span<util::uuid const> const & uuids) const
		{
			return unordered_component_hash<true>(uuids);
		}

		std::uint64_t operator()(std::unique_ptr<table> const & table) const
		{
			return table->hash();
		}
	};

	struct table_hashset_equal
	{
		bool operator()(util::span<util::uuid const> const & uuids, std::unique_ptr<table> const & table) const
		{
			if (uuids.size() != table->columns().size())
				return false;
			for (auto const & uuid : uuids)
				if (!table->column(uuid))
					return false;
			return true;
		}

		bool operator()(std::unique_ptr<table> const & table1, std::unique_ptr<table> const & table2) const
		{
			return table1.get() == table2.get();
		}
	};

	// TODO: store tables in a bitmask trie balanced by subtree size
	struct table_container
	{
		table * get(util::span<util::uuid const> component_uuids);
		table * insert(std::unique_ptr<table> table);

		template <typename Function>
		void apply(Function && function, util::span<util::uuid const> with_uuids, util::span<util::uuid const> without_uuids);

		std::size_t table_count() const;

	private:
		util::hash_set<std::unique_ptr<table>, table_hashset_hash, table_hashset_equal> tables_;
	};

	inline table * table_container::get(util::span<util::uuid const> component_uuids)
	{
		auto it = tables_.find(component_uuids);
		if (it != tables_.end())
			return it->get();
		return nullptr;
	}

	inline table * table_container::insert(std::unique_ptr<table> table)
	{
		auto result = table.get();
		tables_.insert(std::move(table));
		return result;
	}

	template <typename Function>
	void table_container::apply(Function && function, util::span<util::uuid const> with_uuids, util::span<util::uuid const> without_uuids)
	{
		for (auto & table : tables_)
		{
			bool good = true;

			for (auto const & uuid : with_uuids)
				if (!table->column(uuid))
				{
					good = false;
					break;
				}

			for (auto const & uuid : without_uuids)
				if (table->column(uuid))
				{
					good = false;
					break;
				}

			if (good) function(*table);
		}
	}

	inline std::size_t table_container::table_count() const
	{
		return tables_.size();
	}

}
