///|
fn nysiis_is_vowel(letter : UInt16) -> Bool {
  letter == 65 || letter == 69 || letter == 73 || letter == 79 || letter == 85
}

///|
fn copy_ascii_range(value : String, start : Int, end : Int) -> String {
  let mut out = ""
  for i = start; i < end; i = i + 1 {
    out = out + value[i].unsafe_to_char().to_string()
  }
  out
}

///|
fn replace_prefix(
  value : String,
  consumed : Int,
  replacement : String,
) -> String {
  replacement + copy_ascii_range(value, consumed, value.length())
}

///|
fn replace_suffix(
  value : String,
  consumed : Int,
  replacement : String,
) -> String {
  copy_ascii_range(value, 0, value.length() - consumed) + replacement
}

///|
fn prepare_nysiis(value : String) -> String {
  let mut prepared = value
  if prepared.length() >= 3 &&
    prepared[0] == 77 &&
    prepared[1] == 65 &&
    prepared[2] == 67 {
    prepared = replace_prefix(prepared, 3, "MCC")
  } else if prepared.length() >= 2 && prepared[0] == 75 && prepared[1] == 78 {
    prepared = replace_prefix(prepared, 2, "NN")
  } else if prepared[0] == 75 {
    prepared = replace_prefix(prepared, 1, "C")
  } else if prepared.length() >= 2 &&
    prepared[0] == 80 &&
    (prepared[1] == 72 || prepared[1] == 70) {
    prepared = replace_prefix(prepared, 2, "FF")
  } else if prepared.length() >= 3 &&
    prepared[0] == 83 &&
    prepared[1] == 67 &&
    prepared[2] == 72 {
    prepared = replace_prefix(prepared, 3, "SSS")
  }
  let length = prepared.length()
  if length >= 2 &&
    (prepared[length - 2] == 69 || prepared[length - 2] == 73) &&
    prepared[length - 1] == 69 {
    prepared = replace_suffix(prepared, 2, "Y")
  } else if length >= 2 &&
    (
      (prepared[length - 2] == 68 && prepared[length - 1] == 84) ||
      (prepared[length - 2] == 82 && prepared[length - 1] == 84) ||
      (prepared[length - 2] == 82 && prepared[length - 1] == 68) ||
      (prepared[length - 2] == 78 && prepared[length - 1] == 84) ||
      (prepared[length - 2] == 78 && prepared[length - 1] == 68)
    ) {
    prepared = replace_suffix(prepared, 2, "D")
  }
  prepared
}

///|
/// Encodes ASCII letters with the untruncated NYSIIS rules.
pub fn nysiis(input : String) -> String {
  let letters = normalize_ascii_letters(input)
  if letters.length() == 0 {
    return ""
  }
  let prepared = prepare_nysiis(letters)
  let chars : Array[UInt16] = []
  for i = 0; i < prepared.length(); i = i + 1 {
    chars.push(prepared[i])
  }
  let key : Array[UInt16] = [chars[0]]
  for i = 1; i < chars.length(); i = i + 1 {
    let previous = chars[i - 1]
    let current = chars[i]
    let next = if i + 1 < chars.length() { chars[i + 1] } else { 32 }
    let after_next = if i + 2 < chars.length() { chars[i + 2] } else { 32 }
    if current == 69 && next == 86 {
      chars[i] = 65
      chars[i + 1] = 70
    } else if nysiis_is_vowel(current) {
      chars[i] = 65
    } else if current == 81 {
      chars[i] = 71
    } else if current == 90 {
      chars[i] = 83
    } else if current == 77 {
      chars[i] = 78
    } else if current == 75 {
      if next == 78 {
        chars[i] = 78
        chars[i + 1] = 78
      } else {
        chars[i] = 67
      }
    } else if current == 83 && next == 67 && after_next == 72 {
      chars[i] = 83
      chars[i + 1] = 83
      chars[i + 2] = 83
    } else if current == 80 && next == 72 {
      chars[i] = 70
      chars[i + 1] = 70
    } else if current == 72 &&
      (!nysiis_is_vowel(previous) || !nysiis_is_vowel(next)) {
      chars[i] = previous
    } else if current == 87 && nysiis_is_vowel(previous) {
      chars[i] = previous
    }
    if chars[i] != chars[i - 1] {
      key.push(chars[i])
    }
  }
  let mut end = key.length()
  if end > 1 && key[end - 1] == 83 {
    end = end - 1
  }
  if end > 2 && key[end - 2] == 65 && key[end - 1] == 89 {
    key[end - 2] = 89
    end = end - 1
  }
  if end > 1 && key[end - 1] == 65 {
    end = end - 1
  }
  let mut result = ""
  for i = 0; i < end; i = i + 1 {
    result = result + key[i].unsafe_to_char().to_string()
  }
  result
}