{#
# Python has a built-in `enum` module which is nice to use, but doesn't support
# variants with associated data. So, we switch here, and generate a stdlib `enum`
# when none of the variants have associated data, or a generic nested-class
# construct when they do.
#}
{% if e.is_flat() %}

class {{ type_name }}(enum.Enum):
    {%- call py::docstring(e, 4) %}
    {%- for variant in e.variants() %}
    {{ variant.name()|enum_variant_py }} = {{ e|variant_discr_literal(loop.index0) }}
    {%- call py::docstring(variant, 4) %}
    {% endfor %}
{% else %}

class {{ type_name }}:
    {%- call py::docstring(e, 4) %}
    def __init__(self):
        raise RuntimeError("{{ type_name }} cannot be instantiated directly")

    # Each enum variant is a nested class of the enum itself.
    {% for variant in e.variants() -%}
    class {{ variant.name()|enum_variant_py }}:
        {%- call py::docstring(variant, 8) %}

    {%-  if variant.has_nameless_fields() %}
        def __init__(self, *values):
            if len(values) != {{ variant.fields().len() }}:
                raise TypeError(f"Expected {{ variant.fields().len() }} arguments, found {len(values)}")
        {%- for field in variant.fields() %}
            if not isinstance(values[{{ loop.index0 }}], {{ field|type_name }}):
                raise TypeError(f"unexpected type for tuple element {{ loop.index0 }} - expected '{{ field|type_name }}', got '{type(values[{{ loop.index0 }}])}'")
        {%- endfor %}
            self._values = values

        def __getitem__(self, index):
            return self._values[index]

        def __str__(self):
            return f"{{ type_name }}.{{ variant.name()|enum_variant_py }}{self._values!r}"

        def __eq__(self, other):
            if not other.is_{{ variant.name()|var_name }}():
                return False
            return self._values == other._values

    {%-  else -%}
        {%- for field in variant.fields() %}
        {{ field.name()|var_name }}: "{{ field|type_name }}"
        {%- call py::docstring(field, 8) %}
        {%- endfor %}

        def __init__(self,{% for field in variant.fields() %}{{ field.name()|var_name }}: "{{- field|type_name }}"{% if loop.last %}{% else %}, {% endif %}{% endfor %}):
            {%- if variant.has_fields() %}
            {%- for field in variant.fields() %}
            self.{{ field.name()|var_name }} = {{ field.name()|var_name }}
            {%- endfor %}
            {%- else %}
            pass
            {%- endif %}

        def __str__(self):
            return "{{ type_name }}.{{ variant.name()|enum_variant_py }}({% for field in variant.fields() %}{{ field.name()|var_name }}={}{% if loop.last %}{% else %}, {% endif %}{% endfor %})".format({% for field in variant.fields() %}self.{{ field.name()|var_name }}{% if loop.last %}{% else %}, {% endif %}{% endfor %})

        def __eq__(self, other):
            if not other.is_{{ variant.name()|var_name }}():
                return False
            {%- for field in variant.fields() %}
            if self.{{ field.name()|var_name }} != other.{{ field.name()|var_name }}:
                return False
            {%- endfor %}
            return True
    {%  endif %}
    {% endfor %}

    # For each variant, we have an `is_NAME` method for easily checking
    # whether an instance is that variant.
    {% for variant in e.variants() -%}
    def is_{{ variant.name()|var_name }}(self) -> bool:
        return isinstance(self, {{ type_name }}.{{ variant.name()|enum_variant_py }})
    {% endfor %}

# Now, a little trick - we make each nested variant class be a subclass of the main
# enum class, so that method calls and instance checks etc will work intuitively.
# We might be able to do this a little more neatly with a metaclass, but this'll do.
{% for variant in e.variants() -%}
{{ type_name }}.{{ variant.name()|enum_variant_py }} = type("{{ type_name }}.{{ variant.name()|enum_variant_py }}", ({{ type_name }}.{{variant.name()|enum_variant_py}}, {{ type_name }},), {})  # type: ignore
{% endfor %}

{% endif %}

class {{ ffi_converter_name }}(_UniffiConverterRustBuffer):
    @staticmethod
    def read(buf):
        variant = buf.read_i32()

        {%- for variant in e.variants() %}
        if variant == {{ loop.index }}:
            {%- if e.is_flat() %}
            return {{ type_name }}.{{variant.name()|enum_variant_py}}
            {%- else %}
            return {{ type_name }}.{{variant.name()|enum_variant_py}}(
                {%- for field in variant.fields() %}
                {{ field|read_fn }}(buf),
                {%- endfor %}
            )
            {%- endif %}
        {%- endfor %}
        raise InternalError("Raw enum value doesn't match any cases")

    @staticmethod
    def check_lower(value):
        {%- if e.variants().is_empty() %}
        pass
        {%- else %}
        {%- for variant in e.variants() %}
        {%- if e.is_flat() %}
        if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
        {%- else %}
        if value.is_{{ variant.name()|var_name }}():
        {%- endif %}
            {%- for field in variant.fields() %}
            {%- if variant.has_nameless_fields() %}
            {{ field|check_lower_fn }}(value._values[{{ loop.index0 }}])
            {%- else %}
            {{ field|check_lower_fn }}(value.{{ field.name()|var_name }})
            {%- endif %}
            {%- endfor %}
            return
        {%- endfor %}
        raise ValueError(value)
        {%- endif %}

    @staticmethod
    def write(value, buf):
        {%- for variant in e.variants() %}
        {%- if e.is_flat() %}
        if value == {{ type_name }}.{{ variant.name()|enum_variant_py }}:
            buf.write_i32({{ loop.index }})
        {%- else %}
        if value.is_{{ variant.name()|var_name }}():
            buf.write_i32({{ loop.index }})
            {%- for field in variant.fields() %}
            {%- if variant.has_nameless_fields() %}
            {{ field|write_fn }}(value._values[{{ loop.index0 }}], buf)
            {%- else %}
            {{ field|write_fn }}(value.{{ field.name()|var_name }}, buf)
            {%- endif %}
            {%- endfor %}
        {%- endif %}
        {%- endfor %}

