// Internal helpers shared by the parsing, validation and formatting code.
// Nothing here is part of the public API.

///|
/// True when `c` is an ASCII digit.
fn is_digit(c : Char) -> Bool {
  c >= '0' && c <= '9'
}

///|
/// Uppercase an ASCII letter. Every other character (digits, punctuation,
/// non-ASCII) is returned unchanged, so this is safe to run over any input.
fn upper_ascii(c : Char) -> Char {
  match c {
    'a' => 'A'
    'b' => 'B'
    'c' => 'C'
    'd' => 'D'
    'e' => 'E'
    'f' => 'F'
    'g' => 'G'
    'h' => 'H'
    'i' => 'I'
    'j' => 'J'
    'k' => 'K'
    'l' => 'L'
    'm' => 'M'
    'n' => 'N'
    'o' => 'O'
    'p' => 'P'
    'q' => 'Q'
    'r' => 'R'
    's' => 'S'
    't' => 'T'
    'u' => 'U'
    'v' => 'V'
    'w' => 'W'
    'x' => 'X'
    'y' => 'Y'
    'z' => 'Z'
    _ => c
  }
}

///|
/// The characters of a string, in order.
fn chars_of(s : String) -> Array[Char] {
  let out : Array[Char] = []
  for ch in s {
    out.push(ch)
  }
  out
}

///|
/// Build a string from a window of characters. Out-of-range windows clamp, so
/// the result is never longer than `len` and never panics.
fn slice(cs : Array[Char], start : Int, len : Int) -> String {
  let out : Array[Char] = []
  let mut i = if start < 0 { 0 } else { start }
  let end = if start + len > cs.length() { cs.length() } else { start + len }
  while i < end {
    out.push(cs[i])
    i = i + 1
  }
  string_of(out)
}

///|
/// Build a string from characters.
fn string_of(cs : Array[Char]) -> String {
  let buf = StringBuilder()
  for c in cs {
    buf.write_char(c)
  }
  buf.to_string()
}

///|
/// The digits of a string, in order, dropping every non-digit character.
/// Used to accept the punctuation that people habitually type in phone numbers.
fn digits_of(s : String) -> String {
  let buf = StringBuilder()
  for ch in s {
    if is_digit(ch) {
      buf.write_char(ch)
    }
  }
  buf.to_string()
}

///|
/// True when `c` is a character people commonly insert into a written number:
/// spaces, hyphens, dots, slashes and parentheses. Tabs are not included.
fn is_separator(c : Char) -> Bool {
  match c {
    ' ' | '-' | '.' | '/' | '(' | ')' => true
    _ => false
  }
}

///|
/// True when every character is a digit or a separator.
fn digits_and_separators(s : String) -> Bool {
  for c in s {
    if !is_digit(c) && !is_separator(c) {
      return false
    }
  }
  true
}

///|
/// True when `s` starts with `p`.
fn starts_with(s : String, p : String) -> Bool {
  let sc = chars_of(s)
  let pc = chars_of(p)
  if pc.length() > sc.length() {
    return false
  }
  for i = 0; i < pc.length(); i = i + 1 {
    if sc[i] != pc[i] {
      return false
    }
  }
  true
}

///|
/// True when `s` starts with `p`, ignoring ASCII case. The scheme of a URI is
/// case-insensitive, and so are the RFC 3966 parameter names.
fn starts_with_ci(s : String, p : String) -> Bool {
  let sc = chars_of(s)
  let pc = chars_of(p)
  if pc.length() > sc.length() {
    return false
  }
  for i = 0; i < pc.length(); i = i + 1 {
    if upper_ascii(sc[i]) != upper_ascii(pc[i]) {
      return false
    }
  }
  true
}

///|
/// Drop the first `n` characters of `s`. Returns None when the string is
/// shorter than that.
fn drop(s : String, n : Int) -> String? {
  let cs = chars_of(s)
  if n < 0 || n > cs.length() {
    return None
  }
  let buf = StringBuilder()
  for i = n; i < cs.length(); i = i + 1 {
    buf.write_char(cs[i])
  }
  Some(buf.to_string())
}

///|
/// The first `n` characters of `s`. Returns None when the string is shorter.
fn take(s : String, n : Int) -> String? {
  let cs = chars_of(s)
  if n < 0 || n > cs.length() {
    return None
  }
  let buf = StringBuilder()
  for i = 0; i < n; i = i + 1 {
    buf.write_char(cs[i])
  }
  Some(buf.to_string())
}

///|
/// Parse a run of ASCII digits into an integer. Returns None for an empty
/// string or any non-digit, so "0" is 0 and "" is None.
fn int_of_digits(s : String) -> Int? {
  let cs = chars_of(s)
  if cs.length() == 0 {
    return None
  }
  let mut v = 0
  for c in cs {
    if !is_digit(c) {
      return None
    }
    v = v * 10 + (c.to_int() - '0'.to_int())
  }
  Some(v)
}

///|
/// Strip leading and trailing spaces, tabs and newlines.
fn trim(s : String) -> String {
  let cs = chars_of(s)
  let mut start = 0
  let mut end = cs.length()
  while start < end && is_space(cs[start]) {
    start = start + 1
  }
  while end > start && is_space(cs[end - 1]) {
    end = end - 1
  }
  let buf = StringBuilder()
  for i = start; i < end; i = i + 1 {
    buf.write_char(cs[i])
  }
  buf.to_string()
}

///|
/// True for the whitespace characters `trim` removes.
fn is_space(c : Char) -> Bool {
  match c {
    ' ' | '\t' | '\n' | '\r' => true
    _ => false
  }
}

///|
/// Index of the first occurrence of `sub` in `s`, or None.
fn index_of_sub(s : String, sub : String) -> Int? {
  let sc = chars_of(s)
  let pc = chars_of(sub)
  if pc.length() == 0 || pc.length() > sc.length() {
    return None
  }
  for i = 0; i + pc.length() <= sc.length(); i = i + 1 {
    let mut ok = true
    for j = 0; j < pc.length(); j = j + 1 {
      if sc[i + j] != pc[j] {
        ok = false
      }
    }
    if ok {
      return Some(i)
    }
  }
  None
}

///|
/// Split a string on a separator character. Empty fields are kept, so
/// `split_on("a,b", ',')` is `["a", "b"]` and `split_on("a,,b", ',')` has an
/// empty middle field.
fn split_on(s : String, sep : Char) -> Array[String] {
  let cs = chars_of(s)
  let out : Array[String] = []
  let mut start = 0
  for i = 0; i <= cs.length(); i = i + 1 {
    if i == cs.length() || cs[i] == sep {
      let buf = StringBuilder()
      for j = start; j < i; j = j + 1 {
        buf.write_char(cs[j])
      }
      out.push(buf.to_string())
      start = i + 1
    }
  }
  out
}