#pragma once

#include <type_traits>

namespace Juliet
{
    template <typename T>
    concept IsEnum = std::is_enum_v<T>;

    template <IsEnum E>
    constexpr E operator~(E lhs) noexcept
    {
        return static_cast<E>(~static_cast<std::underlying_type_t<E>>(lhs));
    }

    template <IsEnum E>
    constexpr E operator|(E lhs, E rhs) noexcept
    {
        return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) | static_cast<std::underlying_type_t<E>>(rhs));
    }

    template <IsEnum E>
    constexpr E& operator|=(E& lhs, E rhs) noexcept
    {
        return lhs = (lhs | rhs);
    }

    template <IsEnum E>
    constexpr E operator&(E lhs, E rhs) noexcept
    {
        return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) & static_cast<std::underlying_type_t<E>>(rhs));
    }

    template <IsEnum E>
    constexpr E& operator&=(E& lhs, E rhs) noexcept
    {
        return lhs = (lhs & rhs);
    }

    template <IsEnum E>
    constexpr E operator^(E lhs, E rhs) noexcept
    {
        return static_cast<E>(static_cast<std::underlying_type_t<E>>(lhs) ^ static_cast<std::underlying_type_t<E>>(rhs));
    }

    template <IsEnum E>
    constexpr E& operator^=(E& lhs, E rhs) noexcept
    {
        return lhs = (lhs ^ rhs);
    }

    template <IsEnum E>
    constexpr std::underlying_type_t<E> operator-(E lhs, E rhs) noexcept
    {
        using T = std::underlying_type_t<E>;
        return static_cast<T>(static_cast<T>(lhs) - static_cast<T>(rhs));
    }

    template <IsEnum E>
    constexpr std::underlying_type_t<E> operator+(E lhs, E rhs) noexcept
    {
        using T = std::underlying_type_t<E>;
        return static_cast<T>(static_cast<T>(lhs) + static_cast<T>(rhs));
    }

    template <IsEnum E>
    constexpr E operator-(E lhs, std::underlying_type_t<E> rhs) noexcept
    {
        using T = std::underlying_type_t<E>;
        return static_cast<E>(static_cast<T>(static_cast<T>(lhs) - rhs));
    }

    template <IsEnum E>
    constexpr E operator+(E lhs, std::underlying_type_t<E> rhs) noexcept
    {
        using T = std::underlying_type_t<E>;
        return static_cast<E>(static_cast<T>(static_cast<T>(lhs) + rhs));
    }

    template <IsEnum E>
    constexpr std::underlying_type_t<E> ToUnderlying(E enm) noexcept
    {
        return static_cast<std::underlying_type_t<E>>(enm);
    }

    template <IsEnum E>
    constexpr E ToEnum(std::underlying_type_t<E> value) noexcept
    {
        return static_cast<E>(value);
    }
} // namespace Juliet
