///|
fn Enum::unknown_value_variant_name(self : Enum) -> String {
  let names : Map[String, Int] = Map([])
  for value in self.value {
    let value_name = to_camel_case(value.name.unwrap())
    names[value_name] = names.get_or_default(value_name, 0) + 1
  }
  let mut suffix = 0
  while true {
    let name = if suffix == 0 { "Unknown" } else { "Unknown\{suffix}" }
    if !names.contains(name) {
      return name
    }
    suffix += 1
  }
  panic()
}

///|
fn CodeGenerator::gen_enum(
  self : CodeGenerator,
  enum_ : Enum,
  file : StringBuilder,
) -> Unit {
  let derive_list = self.derive_list().join(", ")
  let enum_name = enum_.import_path.path() |> to_camel_case
  let unknown_variant_name = enum_.unknown_value_variant_name()
  let is_open = enum_.is_open()
  file.write_string("pub(all) enum \{enum_name} {\n")
  for value in enum_.value {
    let value_name = to_camel_case(value.name.unwrap())
    file.write_string("  \{value_name}\n")
  }
  if is_open {
    file.write_string("  \{unknown_variant_name}(@lib.Enum)\n")
  }
  file.write_string("} derive(\{derive_list})\n")

  // To enum
  file.write_string(
    (
      $|pub fn \{enum_name}::to_enum(self : \{enum_name}) -> @lib.Enum {
      $|  match self {
      $|
    ),
  )
  for value in enum_.value {
    let value_name = to_camel_case(value.name.unwrap())
    file.write_string(
      "    \{enum_name}::\{value_name} => \{value.number.unwrap()}\n",
    )
  }
  if is_open {
    file.write_string(
      "    \{enum_name}::\{unknown_variant_name}(value) => value\n",
    )
  }
  // From enum
  file.write_string(
    (
      $|  }
      $|}
      $|pub fn \{enum_name}::from_enum(i : @lib.Enum) -> \{enum_name} {
      $|  match i.0 {
      $|
    ),
  )
  for value in enum_.value {
    let value_name = to_camel_case(value.name.unwrap())
    file.write_string(
      "    \{value.number.unwrap()} => \{enum_name}::\{value_name}\n",
    )
  }
  if is_open {
    file.write_string("    _ => \{enum_name}::\{unknown_variant_name}(i)\n")
  } else {
    file.write_string("    _ => Default::default()\n")
  }
  file.write_string("  }\n}\n")

  // Default
  if enum_.value.length() > 0 {
    let first_value_name = to_camel_case(enum_.value[0].name.unwrap())
    file.write_string(
      (
        $|pub impl Default for \{enum_name} with fn default() -> \{enum_name} {
        $|  \{enum_name}::\{first_value_name}
        $|}
        $|
      ),
    )
  }

  // Sized
  file.write_string(
    (
      $|pub impl @lib.Sized for \{enum_name} with fn size_of(self : \{enum_name}) {
      $|  @lib.Sized::size_of(self.to_enum())
      $|}
      $|
    ),
  )

  // From JSON
  if self.support_json() {
    file.write_string(
      "pub impl @json.FromJson for \{enum_name} with fn from_json(json: Json, path: @json.JsonPath) -> \{enum_name} raise {\n",
    )
    file.write_string("  match json {\n")
    for value in enum_.value {
      let value_name = to_camel_case(value.name.unwrap())
      file.write_string(
        "    String(\"\{value_name}\") => \{enum_name}::\{value_name}\n",
      )
    }
    for value in enum_.value {
      let value_name = to_camel_case(value.name.unwrap())
      file.write_string(
        "    Number(\{value.number.unwrap()}, ..) => \{enum_name}::\{value_name}\n",
      )
    }
    if is_open {
      file.write_string(
        (
          $|    Number(value, ..) =>
          $|      if value.trunc() == value {
          $|        \{enum_name}::\{unknown_variant_name}(@lib.Enum(value.to_uint()))
          $|      } else {
          $|        raise @json.JsonDecodeError((path, "Expected an integer number or string for enum"))
          $|      }
          $|
        ),
      )
    }

    // To JSON
    file.write_string(
      (
        $|    _ =>  raise @json.JsonDecodeError((path, "Expected a number or string for enum"))
        $|  }
        $|}
        $|pub impl ToJson for \{enum_name} with fn to_json(self : \{enum_name}) -> Json {
        $|  match self {
        $|
      ),
    )
    for value in enum_.value {
      let value_name = to_camel_case(value.name.unwrap())
      file.write_string(
        "    \{enum_name}::\{value_name} => \"\{value_name}\"\n",
      )
    }
    if is_open {
      file.write_string(
        "    \{enum_name}::\{unknown_variant_name}(value) => value.0.to_json()\n",
      )
    }
    file.write_string(
      (
        #|  }
        #|}
        #|
      ),
    )
  }

  // End
}