#include <psemek/ecs/container.hpp>
#include <psemek/util/assert.hpp>

#include <algorithm>

namespace psemek::ecs
{

	bool container::alive(handle entity) const
	{
		return entity_list_.get_entities()[entity.id].epoch == entity.epoch;
	}

	void container::destroy(handle entity)
	{
		#ifdef PSEMEK_DEBUG
			assert(!currently_changing_archetype_.contains(entity));
			currently_changing_archetype_.insert(entity);
		#endif

		assert(alive(entity));

		++method_recursion_depth_;

		auto const data = entity_list_.get_entities()[entity.id];
		data.table->trigger_destructors(*this, data.row);

		do_destroy(entity);
		entity_list_.destroy(entity.id);

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

		finalize_method();

		--method_recursion_depth_;
	}

	void container::destroy_finally(handle entity)
	{
		finally([entity](container & world){
			if (world.alive(entity))
				world.destroy(entity);
		});
	}

	accessor container::get(handle entity)
	{
		assert(alive(entity));
		auto const data = entity_list_.get_entities()[entity.id];
		return {data.table, data.row};
	}

	std::string container::describe(handle entity) const
	{
		assert(alive(entity));
		auto const data = entity_list_.get_entities()[entity.id];
		return data.table->describe(data.row);
	}

	bool container::can_clone(handle entity) const
	{
		return entity_list_.get_entities()[entity.id].table->non_copyable_components().empty();
	}

	handle container::clone(handle entity)
	{
		if (auto result = try_clone(entity))
			return *result;

		auto const data = entity_list_.get_entities()[entity.id];
		throw entity_not_cloneable(entity, data.table->non_copyable_components());
	}

	std::optional<handle> container::try_clone(handle entity)
	{
		++method_recursion_depth_;

		auto const data = entity_list_.get_entities()[entity.id];
		if (!data.table->non_copyable_components().empty())
			return std::nullopt;

		auto table = data.table;
		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};

		table->copy_row(handle, data.table, data.row);

		table->trigger_constructors(*this, row);

		finalize_method();

		--method_recursion_depth_;

		return handle;
	}

	detail::table * container::insert_table(std::vector<std::unique_ptr<detail::column>> columns)
	{
		auto table = table_container_.insert(std::make_unique<detail::table>(std::move(columns)));

		query_cache_container_.apply([table](detail::query_cache & cache){
			cache.add(table);
		}, [table](util::uuid const & uuid){ return table->column(uuid) != nullptr; });

		return table;
	}

	void container::do_destroy(handle entity)
	{
		auto entities = entity_list_.get_entities();
		auto & data = entities[entity.id];
		auto & iteration_data = data.table->get_iteration_data();

		if (!iteration_data || iteration_data->current_row < data.row)
			remove_row(*data.table, data.row, entities);
		else
			data.table->push_remove(data.row);
	}

	void container::remove_row(detail::table & table, std::uint32_t row, util::span<detail::entity_data> entities)
	{
		// Swap with the last row in that table
		auto table_entity_handles = table.entity_handles();
		auto last_row = table_entity_handles.size() - 1;
		if (row != last_row)
		{
			table.swap_rows(row, last_row);
			auto swap_handle = table_entity_handles[row];
			entities[swap_handle.id].row = row;
		}
		table.pop_row();
	}

	void container::finalize_iteration(detail::table & table)
	{
		auto remove_queue = table.grab_remove_queue();
		std::sort(remove_queue.begin(), remove_queue.end());
		auto entities = entity_list_.get_entities();
		for (auto row : util::reversed(remove_queue))
			remove_row(table, row, entities);

		std::uint32_t row = table.row_count();
		for (auto const & handle : table.flush_delayed())
		{
			auto & data = entities[handle.id];
			data.table = &table;
			data.row = row++;
		}
	}

	void container::finalize_method()
	{
		if (method_recursion_depth_ > 1)
			return;

		while (!finally_callbacks_.empty())
		{
			auto callbacks = std::move(finally_callbacks_);
			finally_callbacks_.clear();

			for (auto & callback : callbacks)
				callback(*this);
		}
	}

	std::size_t container::entity_count()
	{
		return entity_list_.size();
	}

	std::size_t container::cache_count()
	{
		return query_cache_container_.cache_count();
	}

	std::size_t container::table_count()
	{
		return table_container_.table_count();
	}

	statistics container::statistics()
	{
		struct statistics result;

		table_container_.apply([&](detail::table & table)
		{
			result.tables.push_back({table.describe(), table.row_count()});
		}, {}, {});

		return result;
	}

}
