#pragma once

#include <type_traits>
#include <cstdint>

namespace psemek::util
{

	template <typename Flag, typename Repr = std::uint32_t>
	struct flag_set
	{
		using flag_type = Flag;
		using underlying_type = std::conditional_t<std::is_enum_v<Flag>, std::underlying_type_t<Flag>, Flag>;
		using repr_type = Repr;

		repr_type value = repr_type{};

		flag_set() = default;

		flag_set(flag_set const &) = default;

		flag_set(Repr const & repr)
			: value(repr)
		{}

		template <typename ... Flags, typename = std::enable_if_t<(std::is_same_v<Flags, Flag> && ...), void>>
		flag_set(Flags const & ... flags)
			: value{((1 << static_cast<underlying_type>(flags)) | ...)}
		{}

		flag_set & operator = (flag_set const &) = default;

		bool is_set(Flag const & flag) const
		{
			return value & (1 << static_cast<underlying_type>(flag));
		}

		void set(Flag const & flag)
		{
			value |= (1 << static_cast<underlying_type>(flag));
		}

		void flip(Flag const & flag)
		{
			value ^= (1 << static_cast<underlying_type>(flag));
		}

		void unset(Flag const & flag)
		{
			value &= ~(1 << static_cast<underlying_type>(flag));
		}

		void clear()
		{
			value = {};
		}

		flag_set & operator &= (flag_set const & other)
		{
			value &= other.value;
			return *this;
		}

		flag_set & operator |= (flag_set const & other)
		{
			value |= other.value;
			return *this;
		}

		flag_set & operator ^= (flag_set const & other)
		{
			value ^= other.value;
			return *this;
		}
	};

	template <typename Flag, typename Repr>
	flag_set<Flag, Repr> operator ~ (flag_set<Flag, Repr> const & set)
	{
		return {~set.value};
	}

	template <typename Flag, typename Repr>
	flag_set<Flag, Repr> operator | (flag_set<Flag, Repr> const & set1, flag_set<Flag, Repr> const & set2)
	{
		return {set1.value | set2.value};
	}

	template <typename Flag, typename Repr>
	flag_set<Flag, Repr> operator & (flag_set<Flag, Repr> const & set1, flag_set<Flag, Repr> const & set2)
	{
		return {set1.value & set2.value};
	}

	template <typename Flag, typename Repr>
	flag_set<Flag, Repr> operator ^ (flag_set<Flag, Repr> const & set1, flag_set<Flag, Repr> const & set2)
	{
		return {set1.value ^ set2.value};
	}

}
