#pragma once

#include <psemek/ecs/handle.hpp>
#include <psemek/ecs/detail/column.hpp>
#include <psemek/ecs/detail/callback.hpp>
#include <psemek/util/uuid.hpp>
#include <psemek/util/span.hpp>
#include <psemek/util/hash_table.hpp>
#include <psemek/util/assert.hpp>

#include <memory>
#include <vector>
#include <optional>
#include <string>

namespace psemek::ecs::detail
{

	struct table
	{
		table(std::vector<std::unique_ptr<detail::column>> columns);

		std::uint64_t hash() const
		{
			return hash_;
		}

		std::optional<std::size_t> column_index(util::uuid const & uuid) const;
		struct column * column(util::uuid const & uuid) const;

		util::span<handle const> entity_handles() const
		{
			return entity_handles_;
		}

		util::span<std::unique_ptr<detail::column> const> columns() const
		{
			return columns_;
		}

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

		std::size_t push_row(handle handle);
		std::size_t move_row(handle handle, table * from, std::size_t from_row);
		std::size_t copy_row(handle handle, table * from, std::size_t from_row);
		void swap_rows(std::size_t row1, std::size_t row2);
		void pop_row();
		void clear();

		struct iteration_data
		{
			std::size_t current_row = 0;
		};

		std::optional<iteration_data> & get_iteration_data()
		{
			return iteration_data_;
		}

		void push_remove(std::uint32_t row);
		std::vector<std::uint32_t> grab_remove_queue();

		std::unique_ptr<table> clone() const;

		table * get_delayed_table();
		util::span<handle const> flush_delayed();

		std::vector<std::type_index> const & non_copyable_components() const;

		void add_constructor(ordered_table_callback callback);
		void add_destructor(ordered_table_callback callback);

		void trigger_constructors(container & container, std::uint32_t row);
		void trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components);

		void trigger_destructors(container & container, std::uint32_t row);
		void trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components);

		std::size_t memory_usage() const;

		std::string describe() const;
		std::string describe(std::uint32_t row) const;

	protected:
		std::uint64_t hash_;
		std::vector<std::unique_ptr<detail::column>> columns_;
		util::hash_set<util::uuid> column_uuid_set_;
		util::hash_map<util::uuid, std::uint32_t> component_uuid_to_column_index_;

		std::vector<handle> entity_handles_;

		std::optional<iteration_data> iteration_data_;
		std::vector<std::uint32_t> remove_queue_;
		std::unique_ptr<table> delayed_table_;

		std::vector<std::type_index> non_copyable_components_;

		std::shared_ptr<std::vector<ordered_table_callback>> constructors_;
		std::shared_ptr<std::vector<ordered_table_callback>> destructors_;
	};

}
