#pragma once

#include <psemek/util/exception.hpp>
#include <psemek/util/to_string.hpp>
#include <psemek/util/type_name.hpp>

#include <vector>

namespace psemek::util
{

	namespace detail
	{

		template <typename Key>
		std::string key_error_to_string(Key const & key, decltype(util::to_string(key)) *)
		{
			return util::to_string("key ", key, " not found of type ", type_name<Key>());
		}

		template <typename Key>
		std::string key_error_to_string(Key const &, ...)
		{
			return util::to_string("key not found of type ", type_name<Key>());
		}

	}

	struct key_error_base
		: exception
	{
		using exception::exception;
	};

	template <typename Key>
	struct key_error
		: key_error_base
	{
		key_error(Key const & key, util::stacktrace stacktrace = {})
			: key_error_base(detail::key_error_to_string(key), std::move(stacktrace))
			, key_(key)
		{}

		Key const & key() const
		{
			return key_;
		}

	private:
		Key key_;
	};

	template <typename Container, typename Key>
	auto & at(Container && container, Key const & key)
	{
		if (auto it = container.find(key); it != container.end())
			return it->second;

		throw key_error<Key>(key);
	}

	template <typename T, typename Key>
	auto & at(std::vector<T> & container, Key const & key)
	{
		if (key < container.size())
			return container[key];

		throw key_error<Key>(key);
	}

	template <typename T, typename Key>
	auto & at(std::vector<T> const & container, Key const & key)
	{
		if (key < container.size())
			return container[key];

		throw key_error<Key>(key);
	}

}
