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

#include <algorithm>

namespace psemek::ecs::detail
{

	table::table(std::vector<std::unique_ptr<detail::column>> columns)
	{
		unordered_component_hasher<true> hasher;
		for (std::size_t i = 0; i < columns.size(); ++i)
		{
			auto const & column = columns[i];
			auto uuid = column->uuid();
			hasher(uuid);
			column_uuid_set_.insert(uuid);
			component_uuid_to_column_index_.insert({uuid, i});
			if (!column->copy_constructible())
				non_copyable_components_.push_back(column->type());
		}
		hash_ = hasher.result;

		columns_ = std::move(columns);

		constructors_ = std::make_shared<std::vector<ordered_table_callback>>();
		destructors_ = std::make_shared<std::vector<ordered_table_callback>>();
	}

	std::optional<std::size_t> table::column_index(util::uuid const & uuid) const
	{
		if (auto it = component_uuid_to_column_index_.find(uuid); it != component_uuid_to_column_index_.end())
			return it->second;
		return std::nullopt;
	}

	column * table::column(util::uuid const & uuid) const
	{
		if (auto index = column_index(uuid))
			return columns_[*index].get();
		return nullptr;
	}

	std::size_t table::push_row(handle handle)
	{
		for (auto & column : columns_)
			column->push_row();
		entity_handles_.push_back(handle);
		return entity_handles_.size() - 1;
	}

	std::size_t table::move_row(handle handle, table * from, std::size_t from_row)
	{
		for (auto & column : columns_)
		{
			if (auto other_column = from->column(column->uuid()))
				column->emplace_rows(other_column, from_row, 1);
			else
				column->push_row();
		}
		entity_handles_.push_back(handle);
		return entity_handles_.size() - 1;
	}

	std::size_t table::copy_row(handle handle, table * from, std::size_t from_row)
	{
		for (std::size_t i = 0; i < columns_.size(); ++i)
		{
			auto from_column = from->columns_[i].get();
			columns_[i]->insert_rows(from_column, from_row, 1);
		}
		entity_handles_.push_back(handle);
		return entity_handles_.size() - 1;
	}

	void table::swap_rows(std::size_t row1, std::size_t row2)
	{
		for (auto & column : columns_)
			column->swap_rows(row1, row2);
		std::swap(entity_handles_[row1], entity_handles_[row2]);
	}

	void table::pop_row()
	{
		for (auto & column : columns_)
			column->pop_row();
		entity_handles_.pop_back();
	}

	void table::clear()
	{
		for (auto & column : columns_)
			column->clear();
		entity_handles_.clear();
	}

	void table::push_remove(std::uint32_t row)
	{
		remove_queue_.push_back(row);
	}

	std::vector<std::uint32_t> table::grab_remove_queue()
	{
		return std::move(remove_queue_);
	}

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

		auto result = std::make_unique<table>(std::move(columns));
		result->constructors_ = constructors_;
		result->destructors_ = destructors_;
		return result;
	}

	table * table::get_delayed_table()
	{
		if (!delayed_table_)
			delayed_table_ = clone();
		return delayed_table_.get();
	}

	util::span<handle const> table::flush_delayed()
	{
		if (!delayed_table_)
			return {};

		std::size_t count = delayed_table_->row_count();
		for (std::size_t i = 0; i < columns_.size(); ++i)
		{
			auto * src_column = delayed_table_->columns_[i].get();
			columns_[i]->emplace_rows(src_column, 0, count);
		}
		entity_handles_.insert(entity_handles_.end(), delayed_table_->entity_handles_.begin(), delayed_table_->entity_handles_.end());

		delayed_table_->clear();

		return {entity_handles_.data() + entity_handles_.size() - count, count};
	}

	std::vector<std::type_index> const & table::non_copyable_components() const
	{
		return non_copyable_components_;
	}

	void table::add_constructor(ordered_table_callback callback)
	{
		auto it = std::upper_bound(constructors_->begin(), constructors_->end(), callback);
		constructors_->insert(it, std::move(callback));
	}

	void table::add_destructor(ordered_table_callback callback)
	{
		auto it = std::upper_bound(destructors_->begin(), destructors_->end(), callback);
		destructors_->insert(it, std::move(callback));
	}

	void table::trigger_constructors(container & container, std::uint32_t row)
	{
		for (auto const & callback : *constructors_)
			callback.callback(container, *this, row, column_uuid_set_, {}, true);
	}

	void table::trigger_constructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components)
	{
		for (auto const & callback : *constructors_)
			callback.callback(container, *this, row, attached_components, detached_components, false);
	}

	void table::trigger_destructors(container & container, std::uint32_t row)
	{
		for (auto const & callback : *destructors_)
			callback.callback(container, *this, row, {}, column_uuid_set_, true);
	}

	void table::trigger_destructors(container & container, std::uint32_t row, util::hash_set<util::uuid> const & attached_components, util::hash_set<util::uuid> const & detached_components)
	{
		for (auto const & callback : *destructors_)
			callback.callback(container, *this, row, attached_components, detached_components, false);
	}

	std::size_t table::memory_usage() const
	{
		std::size_t result = 0;
		for (auto const & column : columns_)
			result += column->memory_usage();
		return result;
	}

	std::string table::describe() const
	{
		std::string result;
		for (std::size_t i = 0; i < columns_.size(); ++i)
		{
			if (i > 0)
				result += ";";
			result += columns_[i]->describe();
		}
		return result;
	}

	std::string table::describe(std::uint32_t row) const
	{
		std::string result;
		for (std::size_t i = 0; i < columns_.size(); ++i)
		{
			if (i > 0)
				result += ";";
			result += columns_[i]->describe(row);
		}
		return result;
	}

}
