{{- (Globals).Put "enum_override_names" Map -}}

{{- /* ------------------------------------------------------------------ */ -}}
{{-                      define "OverrideEnumName"                           -}}
{{- /* Overrides the C++ name for a sem.Enum.                             */ -}}
{{- /* Arguments:                                                         */ -}}
{{- /* * 'Enum' the sem::Enum                                             */ -}}
{{- /* * 'Name' the new C++ name for enum                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   $enum_override_names := (Globals).Get "enum_override_names" -}}
{{-   $enum_override_names.Put $.Enum $.Name -}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumName"                                -}}
{{- /* Prints the C++ name for the given sem.Enum argument.               */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   $enum_override_names := (Globals).Get "enum_override_names" -}}
{{-   $override := $enum_override_names.Get $ -}}
{{    if $override -}}
{{        $override  -}}
{{    else -}}
{{        PascalCase $.Name}}
{{-   end -}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumCase"                                -}}
{{- /* Prints the 'Enum::kEntry' name for the provided sem.EnumEntry      */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{-   Eval "EnumName" $.Enum}}::k{{PascalCase $.Name}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumFirst"                               -}}
{{- /* Prints the name of the first enum entry                            */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
  kUndefined
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumLast"                                -}}
{{- /* Prints the name of the last enum entry                             */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := $ -}}
{{- $item := index $enum.Entries (Sum -1 (len $enum.Entries)) -}}
  k{{PascalCase $item.Name}}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "DeclareEnum"                             -}}
{{- /* Declares the 'enum class' for the provided sem.Enum argument.      */ -}}
{{- /* The argument can also be a key-value pair with the following keys: */ -}}
{{- /*   "Enum"        - the sem.Enum argument                            */ -}}
{{- /*   "EmitOStream" - (default: true) should operator<< be emitted?    */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := $ -}}
{{- $emit_ostream := true -}}
{{- if Is $ "Map" -}}
{{-   $enum = $.Enum -}}
{{-   $emit_ostream = $.EmitOStream -}}
{{- end }}

{{- $name := Eval "EnumName" $enum -}}
enum class {{$name}} : uint8_t {
    kUndefined,
{{-   range $entry := $enum.Entries }}
    k{{PascalCase $entry.Name}},{{if $entry.IsInternal}}  // Tint-internal enum entry - not parsed{{end}}
{{-   end }}
};

/// @param value the enum value
/// @returns the string for the given enum value
std::string_view ToString({{$name}} value);

{{- if $emit_ostream}}

/// @param out the stream to write to
/// @param value the {{$name}}
/// @returns @p out so calls can be chained
template <typename STREAM>
    requires(traits::IsOStream<STREAM>)
auto& operator<<(STREAM& out, {{$name}} value) {
    return out << ToString(value);
}
{{- end}}

/// Parse{{$name}} parses a {{$name}} from a string.
/// @param str the string to parse
/// @returns the parsed enum, or {{$name}}::kUndefined if the string could not be parsed.
{{$name}} Parse{{$name}}(std::string_view str);

constexpr std::string_view k{{$name}}Strings[] = {
{{-   range $entry := $enum.Entries }}
{{-     if not $entry.IsInternal}}
    "{{$entry.Name}}",
{{-     end }}
{{-   end }}
};

{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                          define "ParseEnum"                              -}}
{{- /* Implements the 'ParseEnum' function for the provided sem.Enum      */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
/// Parse{{$enum}} parses a {{$enum}} from a string.
/// @param str the string to parse
/// @returns the parsed enum, or {{$enum}}::kUndefined if the string could not be parsed.
{{$enum}} Parse{{$enum}}(std::string_view str) {
{{-   range $entry := $.PublicEntries }}
    if (str == "{{$entry.Name}}") {
        return {{template "EnumCase" $entry}};
    }
{{-   end }}
    return {{$enum}}::kUndefined;
}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                         define "EnumOStream"                             -}}
{{- /* Implements the stream 'operator<<()' function to print the         */ -}}
{{- /* provided sem.Enum.                                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
std::string_view ToString({{$enum}} value) {
    switch (value) {
        case {{$enum}}::kUndefined:
            return "undefined";
{{-   range $entry := $.Entries }}
        case {{template "EnumCase" $entry}}:
            return "{{$entry.Name}}";
{{-   end }}
    }
    return "<unknown>";
}
{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                        define "TestParsePrintEnum"                       -}}
{{- /* Implements unit tests for parsing and printing the provided        */ -}}
{{- /* sem.Enum argument.                                                 */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
namespace parse_print_tests {

struct {{$enum}}Case {
    const char* string;
    {{$enum}} value;
};

inline std::ostream& operator<<(std::ostream& out, {{$enum}}Case c) {
    return out << "'" << std::string(c.string) << "'";
}

static constexpr {{$enum}}Case kValid{{$enum}}Cases[] = {
{{-   range $entry := $.PublicEntries }}
    {"{{$entry.Name}}", {{template "EnumCase" $entry}}},
{{-   end }}
};

static constexpr {{$enum}}Case kInvalid{{$enum}}Cases[] = {
{{-   $exclude := $.NameSet -}}
{{-   range $entry := $.PublicEntries }}
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
    {"{{Scramble $entry.Name $exclude}}", {{$enum}}::kUndefined},
{{-   end }}
};

using {{$enum}}ParseTest = testing::TestWithParam<{{$enum}}Case>;

TEST_P({{$enum}}ParseTest, Parse) {
    const char* string = GetParam().string;
    {{$enum}} expect = GetParam().value;
    EXPECT_EQ(expect, Parse{{$enum}}(string));
}

INSTANTIATE_TEST_SUITE_P(Valid{{$enum}}Cases, {{$enum}}ParseTest, testing::ValuesIn(kValid{{$enum}}Cases));
INSTANTIATE_TEST_SUITE_P(Invalid{{$enum}}Cases, {{$enum}}ParseTest, testing::ValuesIn(kInvalid{{$enum}}Cases));

using {{$enum}}PrintTest = testing::TestWithParam<{{$enum}}Case>;

TEST_P({{$enum}}PrintTest, Print) {
    {{$enum}} value = GetParam().value;
    const char* expect = GetParam().string;
    EXPECT_EQ(expect, ToString(value));
}

INSTANTIATE_TEST_SUITE_P(Valid{{$enum}}Cases, {{$enum}}PrintTest, testing::ValuesIn(kValid{{$enum}}Cases));

}  // namespace parse_print_tests

{{- end -}}


{{- /* ------------------------------------------------------------------ */ -}}
{{-                     define "BenchmarkParseEnum"                          -}}
{{- /* Implements a micro-benchmark for parsing the provided sem.Enum     */ -}}
{{- /* argument.                                                          */ -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $enum := Eval "EnumName" $ -}}
void {{$enum}}Parser(::benchmark::State& state) {
    const char* kStrings[] = {
{{-   $exclude := $.NameSet -}}
{{-   range $entry := $.PublicEntries }}
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{$entry.Name}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
        "{{Scramble $entry.Name $exclude}}",
{{-   end }}
    };
    for (auto _ : state) {
        for (auto* str : kStrings) {
            auto result = Parse{{$enum}}(str);
            benchmark::DoNotOptimize(result);
        }
    }
}  // NOLINT(readability/fn_size)

BENCHMARK({{$enum}}Parser);
{{- end -}}
