#pragma once

#include <type_traits>

// This tiny header is to define value ranges at compile time in enums and enum class/struct types.
//
// enum class E1 {
//     A,
//     B,
//     C,
// };
// Y_DEFINE_ENUM_MINMAX(E1, A, C);
//
//     or
//
// enum class E2 {
//     A,
//     B,
//     C,
// };
// Y_DEFINE_ENUM_MAX(E2, C);
//
//     Notes:
//         * use Y_DEFINE_ENUM_MINMAX / Y_DEFINE_ENUM_MINMAX if your enum is defined in a namespace
//         * use Y_DEFINE_ENUM_MINMAX_F / Y_DEFINE_ENUM_MINMAX_F if your enum is defined in a class/struct
//         * use shortened version Y_DEFINE_ENUM_MAX / Y_DEFINE_ENUM_MAX_F if enum begin is 0
//         * add Y_DEFINE_xxx macro immediately after enum definition
//
//     Usage examples:
//         TEnumRange<E>::Min               // min value of range in enum type
//         TEnumRange<E>::Max               // max value of range in enum type
//         TEnumRange<E>::UnderlyingMin     // min value of range in underlying type
//         TEnumRange<E>::UnderlyingMax     // max value of range in underlying type

void _YRegisterEnumRange(...);

namespace NDetail::NEnumRange {

    template <typename E, E _Min, E _Max>
    struct TEnumRange {
        static_assert(std::is_enum_v<E>, "");

        using TEnum = E;
        using TUnderlying = std::underlying_type_t<TEnum>;

        static constexpr TEnum Min = _Min;
        static constexpr TEnum Max = _Max;

        static constexpr TUnderlying UnderlyingMin = static_cast<TUnderlying>(Min);
        static constexpr TUnderlying UnderlyingMax = static_cast<TUnderlying>(Max);

        static_assert(UnderlyingMin <= UnderlyingMax, "Invalid enum range");
    };
}

#define Y_DEFINE_ENUM_MINMAX_IMPL(PREFIX, E, Min, Max) \
    PREFIX ::NDetail::NEnumRange::TEnumRange<E, Min, Max> _YRegisterEnumRange(const E*)

#define Y_DEFINE_ENUM_MINMAX(E, Min, Max) \
    Y_DEFINE_ENUM_MINMAX_IMPL([[maybe_unused]], E, E::Min, E::Max)

#define Y_DEFINE_ENUM_MAX(E, Max) \
    Y_DEFINE_ENUM_MINMAX_IMPL([[maybe_unused]], E, E{}, E::Max)

#define Y_DEFINE_ENUM_MINMAX_FRIEND(E, Min, Max) \
    Y_DEFINE_ENUM_MINMAX_IMPL(friend, E, E::Min, E::Max)

#define Y_DEFINE_ENUM_MAX_FRIEND(E, Max) \
    Y_DEFINE_ENUM_MINMAX_IMPL(friend, E, E{}, E::Max)

template <typename E>
using TEnumRange = decltype(_YRegisterEnumRange(static_cast<E*>(nullptr)));