#include "./default_strategy.hpp"

#include <cassert>

namespace MM::UpdateStrategies {

#define __L_ASSERT(cond) if (!(cond)) { return false; }

SingleThreadedDefault::Graph& SingleThreadedDefault::getGraph(update_phase_t type) {
	using type_t = update_phase_t;

	switch (type) {
		case type_t::PRE:
			return _pre_graph;
		case type_t::MAIN:
			return _main_graph;
		case type_t::POST:
			return _post_graph;
	}

	return _main_graph; // unreachable
}

std::set<update_key_t>& SingleThreadedDefault::getActiveSet(update_phase_t type) {
	using type_t = update_phase_t;

	switch (type) {
		case type_t::PRE:
			return _pre_active;
		case type_t::MAIN:
			return _main_active;
		case type_t::POST:
			return _post_active;
	}

	return _main_active; // unreachable
}

void SingleThreadedDefault::runType(MM::Engine& engine, update_phase_t type) {
	auto aset = getActiveSet(type); // copy
	const auto& graph = getGraph(type);

	while (!aset.empty()) {
		for (const auto key : aset) {
			// check if dependencies are resolved (or dont exist)
			bool resolved = true;
			for (const auto deps : graph.at(key)) {
				if (aset.count(deps)) {
					resolved = false;
					break;
				}
			}

			if (resolved) {
				// do task
				_tasks[key].fn(engine);

				// delete task from set
				aset.erase(key);

				break; // this might be optional but makes the delete easy
			} else {
				// continue the for / dont do anything with this task yet
			}
		}
	}
}

SingleThreadedDefault::~SingleThreadedDefault(void) {
}

bool SingleThreadedDefault::registerService(const entt::id_type s_id, std::vector<UpdateCreationInfo>&& info_array) {
	__L_ASSERT(_service_tasks.count(s_id) == 0);

	// early out
	if (info_array.empty()) {
		return true;
	}

	auto& service_tasks = _service_tasks[s_id];

	for (const UpdateCreationInfo& reg_e : info_array) {
		// test if already exists
		__L_ASSERT(_tasks.count(reg_e.key) == 0);
		// also the graphs
		__L_ASSERT(_pre_graph.count(reg_e.key) == 0);
		__L_ASSERT(_main_graph.count(reg_e.key) == 0);
		__L_ASSERT(_post_graph.count(reg_e.key) == 0);

		// also the enabled taks
		__L_ASSERT(_pre_active.count(reg_e.key) == 0);
		__L_ASSERT(_main_active.count(reg_e.key) == 0);
		__L_ASSERT(_post_active.count(reg_e.key) == 0);

		// potentially check for cicles (can be done by provided decorator)
	}

	for (const UpdateCreationInfo& reg_e : info_array) {
		// add to tasks
		{
			auto& new_task = _tasks[reg_e.key];
			new_task.name = reg_e.name;
			new_task.fn = reg_e.fn;
			new_task.phase = reg_e.phase;
			new_task.auto_enable = reg_e.auto_enable;

			new_task.enabled = false;
		}

		// add relation to service
		service_tasks.emplace_back(reg_e.key);

		// fill in dependencies
		auto& graph = getGraph(reg_e.phase);
		graph[reg_e.key].insert(reg_e.dependencies.begin(), reg_e.dependencies.end());
	}

	return true;
}

bool SingleThreadedDefault::enable(const update_key_t key) {
	__L_ASSERT(_tasks.count(key) == 1);
	__L_ASSERT(_tasks[key].enabled == false);

	auto ret = getActiveSet(_tasks[key].phase).emplace(key);
	_tasks[key].enabled = true;

	return ret.second;
}

bool SingleThreadedDefault::disable(const update_key_t key) {
	__L_ASSERT(_tasks.count(key) == 1);
	__L_ASSERT(_tasks[key].enabled == true);

	auto& aset = getActiveSet(_tasks[key].phase);

	__L_ASSERT(aset.count(key) == 1);

	aset.erase(key);

	_tasks[key].enabled = false;

	return true;
}

bool SingleThreadedDefault::enableService(const entt::id_type s_id) {
	bool succ = true;
	for (const auto id : _service_tasks[s_id]) {
		auto& task = _tasks[id];

		// there should be no task running, if the service is no enabled!!
		assert(!task.enabled);

		if (task.auto_enable) {
			succ &= enable(id);
		}
	}

	return succ;
}

bool SingleThreadedDefault::disableService(const entt::id_type s_id) {
	bool succ = true;
	for (const auto id : _service_tasks[s_id]) {
		auto& task = _tasks[id];

		if (task.auto_enable) {
			assert(task.enabled); // this should never happen
		}

		if (task.enabled) {
			succ &= disable(id);
		}
	}

	return succ;
}

bool SingleThreadedDefault::depend(const update_key_t A, const update_key_t B) {
	// TODO: error checking lol

	if (_tasks.count(A) == 0) {
		return false; // can not add a dependecy of a non existing task
		// TODO: or do we?
	}

	if (_tasks.count(B) == 1) {
		if (_tasks[A].phase != _tasks[B].phase) {
			// cross graph tasks are not allowed
			return false;
		}
	}

	auto& graph = getGraph(_tasks[A].phase);
	auto ret = graph[A].emplace(B);
	return ret.second; // returns whether it was inserted (or already existed)
}

void SingleThreadedDefault::doUpdate(MM::Engine& engine) {
	// pre
	runType(engine, update_phase_t::PRE);

	// main
	runType(engine, update_phase_t::MAIN);

	// post
	runType(engine, update_phase_t::POST);

	if (!_defered_queue.empty()) {
		for (auto&& fn : _defered_queue) {
			fn(engine);
		}

		_defered_queue.clear();
	}
}

void SingleThreadedDefault::addDefered(std::function<void(Engine&)> function) {
	_defered_queue.emplace_back(std::move(function));
}

#undef __L_ASSERT

} // MM::UpdateStrategies

