#include <psemek/rs/registry.hpp>

#include <psemek/util/to_string.hpp>
#include <psemek/util/hash_table.hpp>

#include <map>
#include <mutex>
#include <atomic>

namespace psemek::rs
{

	namespace
	{

		auto & resources_mutex()
		{
			static std::mutex instance;
			return instance;
		}

		auto & resources()
		{
			static std::map<id, resource> instance;
			return instance;
		}

		auto & name_map_mutex()
		{
			static std::mutex instance;
			return instance;
		}

		auto & name_map()
		{
			static util::hash_map<std::string_view, id> instance;
			return instance;
		}

		std::atomic<id> next_id{0};

	}

	unknown_id_error::unknown_id_error(rs::id id, util::stacktrace stacktrace)
		: util::exception(util::to_string("unknown resource id: ", id), std::move(stacktrace))
		, id_(id)
	{}

	unknown_name_error::unknown_name_error(std::string_view name, util::stacktrace stacktrace)
		: util::exception(util::to_string("unknown resource name: ", name), std::move(stacktrace))
	{}

	resource const * find(id id)
	{
		std::lock_guard lock{resources_mutex()};
		auto it = resources().find(id);
		if (it == resources().end())
			return nullptr;
		return &(it->second);
	}

	resource const * find(std::string_view name)
	{
		rs::id id;
		{
			std::lock_guard lock{name_map_mutex()};
			auto it = name_map().find(name);
			if (it == name_map().end())
				return nullptr;
			id = it->second;
		}
		return find(id);
	}

	resource const & get(id id)
	{
		auto result = find(id);
		if (!result)
			throw unknown_id_error{id};
		return *result;
	}

	resource const & get(std::string_view name)
	{
		auto result = find(name);
		if (!result)
			throw unknown_name_error{name};
		return *result;
	}

	id add(std::string_view name, std::string_view data)
	{
		auto id = next_id.fetch_add(1);
		{
			std::lock_guard lock{name_map_mutex()};
			name_map()[name] = id;
		}
		{
			std::lock_guard lock{resources_mutex()};
			resources()[id] = {id, name, data};
		}
		return id;
	}

}
