///|
fn match_rating_remove_vowels(input : String) -> String {
  if input.length() == 0 {
    return ""
  }
  let first = input[0]
  let mut output = ""
  for i = 0; i < input.length(); i = i + 1 {
    let code = input[i]
    if !encoder_ascii_is_vowel(code) || code == 89 {
      output = output + code.unsafe_to_char().to_string()
    }
  }
  if encoder_ascii_is_vowel(first) && first != 89 {
    first.unsafe_to_char().to_string() + output
  } else {
    output
  }
}

///|
fn match_rating_collapse_doubles(input : String) -> String {
  let mut output = ""
  let mut previous : UInt16 = 0
  for i = 0; i < input.length(); i = i + 1 {
    let code = input[i]
    if code != previous {
      output = output + code.unsafe_to_char().to_string()
    }
    previous = code
  }
  output
}

///|
fn match_rating_reduce(input : String) -> String {
  if input.length() > 6 {
    ascii_slice(input, 0, 3) +
    ascii_slice(input, input.length() - 3, input.length())
  } else {
    input
  }
}

///|
/// Generates the at-most-six-character Match Rating codex.
pub fn match_rating_codex(input : String) -> String {
  let letters = normalize_ascii_letters(input)
  if letters.length() == 0 {
    return ""
  }
  match_rating_reduce(
    match_rating_collapse_doubles(match_rating_remove_vowels(letters)),
  )
}

///|
fn match_rating_minimum(sum_length : Int) -> Int {
  if sum_length <= 4 {
    5
  } else if sum_length <= 7 {
    4
  } else if sum_length <= 11 {
    3
  } else if sum_length == 12 {
    2
  } else {
    1
  }
}

///|
fn match_rating_unmatched(chars : Array[UInt16]) -> Int {
  let mut count = 0
  for code in chars {
    if code != 32 {
      count = count + 1
    }
  }
  count
}

///|
fn match_rating_elimination_score(left : String, right : String) -> Int {
  let left_chars : Array[UInt16] = []
  let right_chars : Array[UInt16] = []
  for i = 0; i < left.length(); i = i + 1 {
    left_chars.push(left[i])
  }
  for i = 0; i < right.length(); i = i + 1 {
    right_chars.push(right[i])
  }
  let shared = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  for i = 0; i < shared; i = i + 1 {
    if left[i] == right[i] {
      left_chars[i] = 32
      right_chars[i] = 32
    }
    let left_reverse = left.length() - 1 - i
    let right_reverse = right.length() - 1 - i
    if left[left_reverse] == right[right_reverse] {
      left_chars[left_reverse] = 32
      right_chars[right_reverse] = 32
    }
  }
  let left_unmatched = match_rating_unmatched(left_chars)
  let right_unmatched = match_rating_unmatched(right_chars)
  let maximum = if left_unmatched > right_unmatched {
    left_unmatched
  } else {
    right_unmatched
  }
  6 - maximum
}

///|
/// Compares two names with the Match Rating Approach decision procedure.
pub fn match_rating_compare(left : String, right : String) -> Bool {
  let left_letters = normalize_ascii_letters(left)
  let right_letters = normalize_ascii_letters(right)
  if left_letters.length() < 2 || right_letters.length() < 2 {
    return false
  }
  if left_letters == right_letters {
    return true
  }
  let left_codex = match_rating_codex(left_letters)
  let right_codex = match_rating_codex(right_letters)
  let difference = left_codex.length() - right_codex.length()
  let absolute_difference = if difference < 0 {
    -difference
  } else {
    difference
  }
  if absolute_difference >= 3 {
    return false
  }
  let minimum = match_rating_minimum(left_codex.length() + right_codex.length())
  match_rating_elimination_score(left_codex, right_codex) >= minimum
}