///|
fn tone_vowel_index(base : String) -> Int {
  let mut a_index = -1
  let mut e_index = -1
  let mut o_index = -1
  let mut last_vowel = -1
  for index = 0; index < base.length(); index = index + 1 {
    let code = base[index].to_int()
    if code == 97 {
      a_index = index
    } else if code == 101 || code == 0x00EA {
      e_index = index
    } else if code == 111 {
      o_index = index
    }
    if code == 97 ||
      code == 101 ||
      code == 105 ||
      code == 111 ||
      code == 117 ||
      code == 0x00FC {
      last_vowel = index
    }
  }
  if a_index >= 0 {
    return a_index
  }
  if e_index >= 0 {
    return e_index
  }
  if o_index >= 0 && syllable_ascii_slice(base, o_index, o_index + 2) == "ou" {
    return o_index
  }
  if last_vowel < 0 {
    if base == "m" || base == "n" || base == "ng" {
      return 0
    }
    if syllable_ends_with(base, "ng") {
      return base.length() - 2
    }
    if syllable_ends_with(base, "m") || syllable_ends_with(base, "n") {
      return base.length() - 1
    }
  }
  last_vowel
}

///|
fn tone_mark_code(base_code : Int, tone : Int) -> Int {
  match (base_code, tone) {
    (97, 1) => 0x0101
    (97, 2) => 0x00E1
    (97, 3) => 0x01CE
    (97, 4) => 0x00E0
    (101, 1) => 0x0113
    (101, 2) => 0x00E9
    (101, 3) => 0x011B
    (101, 4) => 0x00E8
    (105, 1) => 0x012B
    (105, 2) => 0x00ED
    (105, 3) => 0x01D0
    (105, 4) => 0x00EC
    (111, 1) => 0x014D
    (111, 2) => 0x00F3
    (111, 3) => 0x01D2
    (111, 4) => 0x00F2
    (117, 1) => 0x016B
    (117, 2) => 0x00FA
    (117, 3) => 0x01D4
    (117, 4) => 0x00F9
    (0x00FC, 1) => 0x01D6
    (0x00FC, 2) => 0x01D8
    (0x00FC, 3) => 0x01DA
    (0x00FC, 4) => 0x01DC
    (110, 2) => 0x0144
    (110, 3) => 0x0148
    (110, 4) => 0x01F9
    (109, 2) => 0x1E3F
    (0x00EA, 2) => 0x1EBF
    (0x00EA, 4) => 0x1EC1
    _ => base_code
  }
}

///|
fn uppercase_code(code : Int) -> Int {
  match code {
    97..=122 => code - 32
    0x0101 => 0x0100
    0x00E1 => 0x00C1
    0x01CE => 0x01CD
    0x00E0 => 0x00C0
    0x0113 => 0x0112
    0x00E9 => 0x00C9
    0x011B => 0x011A
    0x00E8 => 0x00C8
    0x012B => 0x012A
    0x00ED => 0x00CD
    0x01D0 => 0x01CF
    0x00EC => 0x00CC
    0x014D => 0x014C
    0x00F3 => 0x00D3
    0x01D2 => 0x01D1
    0x00F2 => 0x00D2
    0x016B => 0x016A
    0x00FA => 0x00DA
    0x01D4 => 0x01D3
    0x00F9 => 0x00D9
    0x00FC => 0x00DC
    0x01D6 => 0x01D5
    0x01D8 => 0x01D7
    0x01DA => 0x01D9
    0x01DC => 0x01DB
    0x00EA => 0x00CA
    0x0144 => 0x0143
    0x0148 => 0x0147
    0x01F9 => 0x01F8
    0x1E3F => 0x1E3E
    0x1EBF => 0x1EBE
    0x1EC1 => 0x1EC0
    _ => code
  }
}

///|
fn format_uppercase(text : String) -> String {
  let mut output = ""
  for index = 0; index < text.length(); index = index + 1 {
    output = output +
      uppercase_code(text[index].to_int())
      .to_uint16()
      .unsafe_to_char()
      .to_string()
  }
  output
}

///|
/// Renders a validated syllable in a requested tone style and case.
fn format_numbered(base : String, tone : Int) -> String {
  if tone == 0 {
    base
  } else {
    base + tone.to_string()
  }
}

///|
fn format_marked(base : String, tone : Int) -> String {
  if tone == 0 {
    return base
  }
  let mark_index = tone_vowel_index(base)
  let mut marked = ""
  for index = 0; index < base.length(); index = index + 1 {
    let code = base[index].to_int()
    let output_text = if index == mark_index &&
      (
        (code == 0x00EA && (tone == 1 || tone == 3)) ||
        (code == 109 && tone != 2) ||
        (code == 110 && tone == 1)
      ) {
      let combining = match tone {
        1 => 0x0304
        2 => 0x0301
        3 => 0x030C
        _ => 0x0300
      }
      code.to_uint16().unsafe_to_char().to_string() +
      combining.to_uint16().unsafe_to_char().to_string()
    } else {
      let output_code = if index == mark_index {
        tone_mark_code(code, tone)
      } else {
        code
      }
      output_code.to_uint16().unsafe_to_char().to_string()
    }
    marked = marked + output_text
  }
  marked
}

///|
pub fn Syllable::format(
  self : Syllable,
  style : ToneStyle,
  text_case : TextCase,
) -> String {
  let result = match style {
    PlainPinyin => self.base_value
    ToneNumbers => format_numbered(self.base_value, self.tone_value)
    ToneMarks => format_marked(self.base_value, self.tone_value)
  }
  match text_case {
    Lowercase => result
    Uppercase => format_uppercase(result)
  }
}