///|
fn upper_ascii(s : String) -> String {
  let mut out = ""
  let mut i = 0
  while i < s.length() {
    let c = s[i]
    if c >= 'a' && c <= 'z' {
      let mut k = 0
      let lower = "abcdefghijklmnopqrstuvwxyz"
      while k < 26 {
        if lower[k] == c {
          out = out + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[k:k + 1].to_owned()
          break
        }
        k += 1
      }
    } else {
      out = out + unit_at(s, i)
    }
    i += 1
  }
  out
}

///|
fn trim_ascii(s : String) -> String {
  let mut start = 0
  let mut end = s.length()
  while start < end && (s[start] == ' ' || s[start] == '\t') {
    start += 1
  }
  while end > start && (s[end - 1] == ' ' || s[end - 1] == '\t') {
    end -= 1
  }
  s[start:end].to_owned()
}

///|
fn split_raw_lines(input : String) -> Array[String] {
  let lines : Array[String] = []
  let mut start = 0
  let mut i = 0
  while i < input.length() {
    let c = input[i]
    if c == '\n' || c == '\r' {
      lines.push(input[start:i].to_owned())
      if c == '\r' && i + 1 < input.length() && input[i + 1] == '\n' {
        i += 1
      }
      start = i + 1
    }
    i += 1
  }
  if start <= input.length() {
    lines.push(input[start:input.length()].to_owned())
  }
  let out : Array[String] = []
  i = 0
  while i < lines.length() {
    let t = trim_ascii(lines[i])
    if t.length() > 0 {
      out.push(upper_ascii(t))
    }
    i += 1
  }
  out
}

///|
fn expand_single_line(line : String) -> Array[String]? {
  if line.length() == 88 {
    return Some([line[0:44].to_owned(), line[44:88].to_owned()])
  }
  if line.length() == 72 {
    return Some([line[0:36].to_owned(), line[36:72].to_owned()])
  }
  if line.length() == 90 {
    return Some([
      line[0:30].to_owned(),
      line[30:60].to_owned(),
      line[60:90].to_owned(),
    ])
  }
  None
}

///|
fn detect_layout(lines : Array[String]) -> Layout? {
  if lines.length() == 2 && lines[0].length() == 44 && lines[1].length() == 44 {
    return Some(TD3)
  }
  if lines.length() == 2 && lines[0].length() == 36 && lines[1].length() == 36 {
    return Some(TD2)
  }
  if lines.length() == 3 &&
    lines[0].length() == 30 &&
    lines[1].length() == 30 &&
    lines[2].length() == 30 {
    return Some(TD1)
  }
  None
}

///|
fn rtrim_fillers(s : String) -> String {
  let mut end = s.length()
  while end > 0 && s[end - 1] == '<' {
    end -= 1
  }
  s[0:end].to_owned()
}

///|
fn humanize_fillers(s : String) -> String {
  let mut out = ""
  let mut i = 0
  let mut space = false
  while i < s.length() {
    let c = s[i]
    if c == '<' {
      space = true
    } else {
      if space && out.length() > 0 {
        out = out + " "
      }
      out = out + unit_at(s, i)
      space = false
    }
    i += 1
  }
  out
}

///|
fn split_names(field : String) -> Names {
  let mut i = 0
  while i + 1 < field.length() {
    if field[i] == '<' && field[i + 1] == '<' {
      return {
        primary: humanize_fillers(field[0:i].to_owned()),
        secondary: humanize_fillers(field[i + 2:field.length()].to_owned()),
      }
    }
    i += 1
  }
  { primary: humanize_fillers(field), secondary: "" }
}

///|
fn pad_fillers(s : String, width : Int) -> String {
  if s.length() >= width {
    return s[0:width].to_owned()
  }
  let mut out = s
  while out.length() < width {
    out = out + "<"
  }
  out
}

///|
fn spaces_to_fillers(s : String) -> String {
  let mut out = ""
  let mut i = 0
  while i < s.length() {
    if s[i] == ' ' {
      out = out + "<"
    } else {
      out = out + unit_at(s, i)
    }
    i += 1
  }
  out
}