#pragma once

#include <psemek/util/hash_table.hpp>

#include <typeindex>
#include <any>

namespace psemek::util
{

	struct any_set
	{
		template <typename T>
		T & insert(T value);

		template <typename T, typename ... Args>
		T & emplace(Args && ... args);

		template <typename T>
		T const & get() const;

		template <typename T>
		T & get();

		template <typename T>
		T const * get_if() const;

		template <typename T>
		T * get_if();

		template <typename T>
		bool contains() const;

		template <typename T>
		void remove();

	private:
		util::hash_map<std::type_index, std::any> storage_;
	};

	template <typename T>
	T & any_set::insert(T value)
	{
		return std::any_cast<T&>(storage_[typeid(std::remove_cvref_t<T>)] = std::move(value));
	}

	template <typename T, typename ... Args>
	T & any_set::emplace(Args && ... args)
	{
		return std::any_cast<T &>(storage_[typeid(T)] = T(std::forward<Args>(args)...));
	}

	template <typename T>
	T const & any_set::get() const
	{
		return std::any_cast<T const &>(storage_.at(typeid(T)));
	}

	template <typename T>
	T & any_set::get()
	{
		return std::any_cast<T &>(storage_.at(typeid(T)));
	}

	template <typename T>
	T const * any_set::get_if() const
	{
		if (auto it = storage_.find(typeid(T)); it != storage_.end())
			return std::addressof(std::any_cast<T const &>(it->second));
		return nullptr;
	}

	template <typename T>
	T * any_set::get_if()
	{
		if (auto it = storage_.find(typeid(T)); it != storage_.end())
			return std::addressof(std::any_cast<T &>(it->second));
		return nullptr;
	}

	template <typename T>
	bool any_set::contains() const
	{
		return storage_.contains(typeid(T));
	}

	template <typename T>
	void any_set::remove()
	{
		storage_.erase(typeid(T));
	}

}
