//----- Classic macro code -----

// Force evaluation of an expression
#define $eval(...) $eval_1($eval_1($eval_1(__VA_ARGS__)))
#define $eval_1(...) $eval_2($eval_2($eval_2(__VA_ARGS__)))
#define $eval_2(...) $eval_3($eval_3($eval_3(__VA_ARGS__)))
#define $eval_3(...) $eval_4($eval_4($eval_4(__VA_ARGS__)))
#define $eval_4(...) $eval_5($eval_5($eval_5(__VA_ARGS__)))
#define $eval_5(...) $eval_6($eval_6($eval_6(__VA_ARGS__)))
#define $eval_6(...) __VA_ARGS__

// Concatenate two words.
#define $cat(a, ...) $cat_(a, __VA_ARGS__)
#define $cat_(a, ...) a##__VA_ARGS__

// Count arguments.
#define $args_count(...) $args_count_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define $args_count_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, n, ...) n

#define $check_(x, n, ...) n
#define $check(...) $check_(__VA_ARGS__, 0)
#define $probe(x) x, 1,

#define $void()
#define $defer(id) id $void()
#define $obstruct(...) __VA_ARGS__ $defer($void)()

/// Boolean operations.
#define $not(x) $check($cat($not_, x))
#define $not_0 $probe(~)

#define $not_bool(n) $check($cat($not_bool_, n))
#define $not_bool_0 $probe(~)
#define $bool(n) $not($not_bool(n))

/// Strings operations.
#define $is_string_empty(s) $check($is_string_empty_##s)
#define $is_string_empty_ $probe(~)

#define $are_arguments_empty(...) $cat($are_arguments_empty_, $bool($args_count(__VA_ARGS__)))(__VA_ARGS__)
#define $are_arguments_empty_0(...) 0
#define $are_arguments_empty_1(value, ...) $not($is_string_empty(value))

//----- Enum code -----

#include <ostream>

/**
 * Generates an enum class with stringify functions.
 *
 * ```c++
 * $enum(PowerLevel,
 *      High,
 *      Low,
 * );
 * ```
 *
 * will generate:
 *
 * ```c++
 * enum class PowerLevel {
 *     High,
 *     Low,
 * };
 *
 * constexpr const char* stringify(PowerLevel value)
 * {
 *     switch (value) {
 *     case PowerLevel::Low: {
 *         return "PowerLevel::Low";
 *     };
 *     case PowerLevel::High: {
 *         return "PowerLevel::High";
 *     };
 *     default: break;
 *     }
 *     return "PowerLevel::<Unknown>";
 * };
 *
 * inline std::ostream& operator<<(std::ostream& os, PowerLevel value)
 * {
 *     os << stringify(value);
 *     return os;
 * };
 * ```
 */
#define $enum(...)                \
    $enum_enum(__VA_ARGS__);      \
    $enum_stringify(__VA_ARGS__); \
    $enum_ostream(__VA_ARGS__);

//---- enum: enum

#define $enum_enum(Enum, ...) \
    enum Enum                 \
    {                         \
        __VA_ARGS__           \
    };

//---- enum: stringify

#define $enum_stringify(Enum, ...)                    \
    constexpr const char *stringify(Enum value)       \
    {                                                 \
        switch (value)                                \
        {                                             \
            $enum_stringify_cases(Enum, __VA_ARGS__); \
        default:                                      \
            break;                                    \
        }                                             \
        return #Enum "::<Unknown>";                   \
    }

#define $enum_stringify_cases(Enum, ...) $eval($enum_stringify_cases_(Enum, __VA_ARGS__))
#define $enum_stringify_cases_(Enum, ...) $enum_stringify_case_($are_arguments_empty(__VA_ARGS__), Enum, __VA_ARGS__)
#define $enum_stringify_cases_indirect() $enum_stringify_cases_

#define $enum_stringify_case_(n, Enum, ...) $cat($enum_stringify_case_, n)(Enum, __VA_ARGS__)
#define $enum_stringify_case_0(Enum, ...) /* Empty end case */
#define $enum_stringify_case_1(Enum, Value, ...) \
    $enum_stringify_case(Enum, Value);           \
    $obstruct($enum_stringify_cases_indirect)()(Enum, __VA_ARGS__)

#define $enum_stringify_case(Enum, Value) $cat($enum_stringify_case_check_, $is_string_empty(Value))(Enum, Value)
#define $enum_stringify_case_check_1(...) /* Empty end case */
#define $enum_stringify_case_check_0(Enum, Value) \
    case Enum::Value:                             \
    {                                             \
        return #Enum "::" #Value;                 \
    }

//---- enum: ostream

#define $enum_ostream(Enum, ...)                                  \
    inline std::ostream &operator<<(std::ostream &os, Enum value) \
    {                                                             \
        os << stringify(value);                                   \
        return os;                                                \
    }

//----- Main file -----

#include <iostream>

$enum(PowerLevel,
      High,
      Low);

int main(void)
{
    PowerLevel powerLevel = PowerLevel::High;
    std::cout << "Current power level: " << powerLevel << std::endl;

    return 0;
}
