#include <vector>
#include "MacroUtils.hpp"
#include "StringView.hpp"

#define ENUM_VALUE(x, value) x = value,
#
#define ENUM_CASE(x, name)                                                                                             \
    case name::x:                                                                                                      \
        return ArbUt::StringViewLiteral(#x);
#
#define ENUM_PARSE_CASE(x, name)                                                                                       \
    case ConstHash(#x):                                                                                                \
        return name::x;
#
#define ENUM_TRY_PARSE_CASE(x, name)                                                                                   \
    case ConstHash(#x):                                                                                                \
        out = name::x;                                                                                                 \
        return true;
#
#define ENUM_PARSE_CASE_INSENSITIVE(x, name)                                                                           \
    case ConstHashCI(#x):                                                                                              \
        return name::x;
#
#define ENUM_TRY_PARSE_CASE_INSENSITIVE(x, name)                                                                       \
    case ConstHashCI(#x):                                                                                              \
        out = name::x;                                                                                                 \
        return true;
#
#define ENUM_GET_HIGHEST(x, name)                                                                                      \
    if ((i64)name::x > highest) {                                                                                      \
        highest = (i64)name::x;                                                                                        \
    }
#
#define ENUM_GET_LOWEST(x, name)                                                                                       \
    if ((i64)name::x < lowest) {                                                                                       \
        lowest = (i64)name::x;                                                                                         \
    }
#
#define ARRAY_NAME(x, name) name::x,

/*!
  \def ENUM(name, type, values...)
  Generates an enum class inheriting from specified type with specified name. Also generates a useful helper class with
  static methods for use with the enum.
*/

#if defined(__clang__)
#define ALLOW_UINTEGER_OVERFLOW __attribute__((no_sanitize("unsigned-integer-overflow")))
#else
#define ALLOW_UINTEGER_OVERFLOW
#endif

#define ENUM_WITH_START_VALUE(name, type, startValue, ...)                                                             \
    enum class name : type {                                                                                           \
        MACRO_UTILS_FOR_EACH_WITH_VALUE(ENUM_VALUE, startValue + ___MACRO_UTILS_NARGS(__VA_ARGS__) - 1, __VA_ARGS__)        \
    };                                                                                                                 \
    class name##Helper {                                                                                               \
        ALLOW_UINTEGER_OVERFLOW                                                                                        \
        inline static u32 constexpr ConstHash(char const* non_null input) noexcept {                                   \
            return *input ? static_cast<u32>(*input) + 33 * ConstHash(input + 1) : 5381;                               \
        }                                                                                                              \
        inline static constexpr char charToLower(const char c) noexcept {                                              \
            return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c;                                                       \
        }                                                                                                              \
        ALLOW_UINTEGER_OVERFLOW                                                                                        \
        inline static u32 constexpr ConstHashCI(char const* non_null input) noexcept {                                 \
            return charToLower(*input) ? static_cast<u32>(charToLower(*input)) + 33 * ConstHashCI(input + 1) : 5381;   \
        }                                                                                                              \
                                                                                                                       \
    public:                                                                                                            \
        constexpr static ArbUt::StringViewLiteral ToString(name value) noexcept {                                      \
            switch (value) { MACRO_UTILS_FOR_EACH(ENUM_CASE, name, __VA_ARGS__) }                                      \
            return "out of bounds"_cnc;                                                                                \
        }                                                                                                              \
        constexpr static name Parse(const char* non_null input, bool caseInsensitive = false) {                        \
            if (caseInsensitive)                                                                                       \
                return ParseCaseInsensitive(input);                                                                    \
            switch (ConstHash(input)) { MACRO_UTILS_FOR_EACH(ENUM_PARSE_CASE, name, __VA_ARGS__) }                     \
            throw std::runtime_error("Invalid " #name " string.");                                                     \
        }                                                                                                              \
        constexpr static bool TryParse(const char* non_null input, name& out, bool caseInsensitive = false) noexcept { \
            if (caseInsensitive)                                                                                       \
                return TryParseCaseInsensitive(input, out);                                                            \
            switch (ConstHash(input)) { MACRO_UTILS_FOR_EACH(ENUM_TRY_PARSE_CASE, name, __VA_ARGS__) }                 \
            return false;                                                                                              \
        }                                                                                                              \
        static std::vector<name> GetValues() noexcept {                                                                \
            return {MACRO_UTILS_FOR_EACH(ARRAY_NAME, name, __VA_ARGS__)};                                              \
        }                                                                                                              \
        constexpr static name Last() noexcept { return name::MACRO_UTILS_GET_LAST(__VA_ARGS__); }                      \
        constexpr static name First() noexcept { return name::MACRO_UTILS_GET_FIRST(__VA_ARGS__); }                    \
        constexpr static name Highest() noexcept {                                                                     \
            i64 highest = -9223372036854775807;                                                                        \
            MACRO_UTILS_FOR_EACH(ENUM_GET_HIGHEST, name, __VA_ARGS__)                                                  \
            return (name)highest;                                                                                      \
        }                                                                                                              \
        constexpr static name Lowest() noexcept {                                                                      \
            i64 lowest = 9223372036854775807;                                                                          \
            MACRO_UTILS_FOR_EACH(ENUM_GET_LOWEST, name, __VA_ARGS__)                                                   \
            return (name)lowest;                                                                                       \
        }                                                                                                              \
                                                                                                                       \
    private:                                                                                                           \
        constexpr static name ParseCaseInsensitive(const char* non_null input) {                                       \
            switch (ConstHashCI(input)) { MACRO_UTILS_FOR_EACH(ENUM_PARSE_CASE_INSENSITIVE, name, __VA_ARGS__) }       \
            throw std::runtime_error("Invalid " #name " string.");                                                     \
        }                                                                                                              \
        constexpr static bool TryParseCaseInsensitive(const char* non_null input, name& out) noexcept {                \
            switch (ConstHashCI(input)) { MACRO_UTILS_FOR_EACH(ENUM_TRY_PARSE_CASE_INSENSITIVE, name, __VA_ARGS__) }   \
            return false;                                                                                              \
        }                                                                                                              \
    };

#define ENUM(name, type, ...) ENUM_WITH_START_VALUE(name, type, 0, __VA_ARGS__);