/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <array>
#include <cassert>
#include <iosfwd>
#include <string_view>
#include <type_traits>

/// Enum macro specifically for state-machines.
/// Has several limitations, some intentional. For example,
/// the states must have automatic, sequential, values.
/// But also has some advantages, for example it can be used inside classes.
/// Some ideas from https://stackoverflow.com/questions/28828957/enum-to-string-in-modern-c11-c14-c17-and-future-c20
/// and from https://github.com/pfultz2/Cloak/wiki/C-Preprocessor-tricks,-tips,-and-idioms

// Macro expansion doesn't happen when # or ## operators are used,
// so we need an indirection to expand macros before using the result.
#define CONCATINATE_IMPL(X, Y) X##Y
#define CONCATINATE(X, Y) CONCATINATE_IMPL(X, Y)
#define STRINGIFY(X) #X
#define STRING(X) STRINGIFY(X)

#define STRINGIFY1(_, e) STRINGIFY(e),
#define STRINGIFY2(NAME, e) #NAME "::" #e,
#define CALL(X, ...) X(__VA_ARGS__)

#define APPLY1(MACRO, NAME, e) MACRO(NAME, e)
#define APPLY2(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY1(MACRO, NAME, __VA_ARGS__)
#define APPLY3(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY2(MACRO, NAME, __VA_ARGS__)
#define APPLY4(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY3(MACRO, NAME, __VA_ARGS__)
#define APPLY5(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY4(MACRO, NAME, __VA_ARGS__)
#define APPLY6(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY5(MACRO, NAME, __VA_ARGS__)
#define APPLY7(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY6(MACRO, NAME, __VA_ARGS__)
#define APPLY8(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY7(MACRO, NAME, __VA_ARGS__)
#define APPLY9(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY8(MACRO, NAME, __VA_ARGS__)
#define APPLY10(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY9(MACRO, NAME, __VA_ARGS__)
#define APPLY11(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY10(MACRO, NAME, __VA_ARGS__)
#define APPLY12(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY11(MACRO, NAME, __VA_ARGS__)
#define APPLY13(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY12(MACRO, NAME, __VA_ARGS__)
#define APPLY14(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY13(MACRO, NAME, __VA_ARGS__)
#define APPLY15(MACRO, NAME, e, ...) MACRO(NAME, e) APPLY14(MACRO, NAME, __VA_ARGS__)

// Credit to Anton Bachin for this trick.
#define GET_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, c, ...) c
#define COUNT_ARGS(...) GET_COUNT(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

#define APPLY(MACRO, NAME, ...)                                                                    \
    CALL(CONCATINATE, APPLY, COUNT_ARGS(__VA_ARGS__))(MACRO, NAME, __VA_ARGS__)
#define FOR_EACH(MACRO, NAME, ...) APPLY(MACRO, NAME, __VA_ARGS__)

/// Define a state-machine with various independent states.
/// NAME is the name of the state enum followed by the state names.
#define STATE_ENUM(NAME, ...)                                                                      \
    enum class NAME : char;                                                                        \
    /* Returns the state name only, without the namespace. */                                      \
    [[maybe_unused]] static inline constexpr std::string_view nameShort(NAME elm) noexcept         \
    {                                                                                              \
        constexpr std::array<std::string_view, COUNT_ARGS(__VA_ARGS__)> NAME##_names = { FOR_EACH( \
            STRINGIFY1, NAME, __VA_ARGS__) };                                                      \
        assert(static_cast<unsigned>(elm) < NAME##_names.size() && "Enum value is out of range."); \
        return NAME##_names[static_cast<int>(elm)];                                                \
    }                                                                                              \
    /* Returns the state name with the namespace. */                                               \
    [[maybe_unused]] static inline constexpr std::string_view name(NAME elm) noexcept              \
    {                                                                                              \
        constexpr std::array<std::string_view, COUNT_ARGS(__VA_ARGS__)> NAME##_names = { FOR_EACH( \
            STRINGIFY2, NAME, __VA_ARGS__) };                                                      \
        assert(static_cast<unsigned>(elm) < NAME##_names.size() && "Enum value is out of range."); \
        return NAME##_names[static_cast<int>(elm)];                                                \
    }                                                                                              \
    [[maybe_unused]] static constexpr size_t NAME##Max = COUNT_ARGS(__VA_ARGS__);                  \
    enum class NAME : char                                                                         \
    {                                                                                              \
        __VA_ARGS__                                                                                \
    }

/// Support seamless serialization of STATE_ENUM to ostream.
template <typename T>
inline std::ostream& operator<<(std::ostream& os, const T state)
    requires std::is_same_v<decltype(name(T())), std::string_view>
{
    os << name(state);
    return os;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
