// Small text utilities used by the line-oriented parser and matcher.

///|
fn lower_ascii(input : String) -> String {
  let output : Array[Char] = []
  for ch in input {
    let code = ch.to_int()
    if code >= 'A'.to_int() && code <= 'Z'.to_int() {
      output.push((code + 32).unsafe_to_char())
    } else {
      output.push(ch)
    }
  }
  String::from_array(output)
}

///|
fn strip_utf8_bom(line : String) -> String {
  let chars = line.to_array()
  if chars.length() > 0 && chars[0] == '\u{FEFF}' {
    String::from_array(chars[1:])
  } else {
    line
  }
}

///|
fn strip_one_trailing_cr(line : String) -> String {
  if line.has_suffix("\r") {
    let chars = line.to_array()
    String::from_array(chars[0:chars.length() - 1])
  } else {
    line
  }
}

///|
fn strip_comment(line : String) -> String {
  match line.split_once("#") {
    Some(parts) => parts.0.to_owned()
    None => line
  }
}

///|
fn inline_comment_count(line : String) -> Int {
  if line.contains("#") {
    1
  } else {
    0
  }
}

///|
fn is_ascii_letter(ch : Char) -> Bool {
  let code = ch.to_int()
  (code >= 'A'.to_int() && code <= 'Z'.to_int()) ||
  (code >= 'a'.to_int() && code <= 'z'.to_int())
}

///|
fn is_ascii_digit(ch : Char) -> Bool {
  let code = ch.to_int()
  code >= '0'.to_int() && code <= '9'.to_int()
}

///|
fn is_agent_char(ch : Char) -> Bool {
  is_ascii_letter(ch) ||
  is_ascii_digit(ch) ||
  ch == '-' ||
  ch == '_' ||
  ch == '.'
}

///|
fn valid_product_token(token : String) -> Bool {
  if token == "*" {
    return true
  }
  if token.length() == 0 {
    return false
  }
  for ch in token {
    if !is_agent_char(ch) {
      return false
    }
  }
  true
}

///|
fn is_record_name_char(ch : Char) -> Bool {
  is_ascii_letter(ch) || is_ascii_digit(ch) || ch == '-'
}

///|
fn valid_record_name(name : String) -> Bool {
  if name.length() == 0 {
    return false
  }
  for ch in name {
    if !is_record_name_char(ch) {
      return false
    }
  }
  true
}

///|
fn contains_illegal_control(input : String) -> Bool {
  for ch in input {
    let code = ch.to_int()
    if (code >= 0 && code < 32 && ch != '\t' && ch != '\r' && ch != '\n') ||
      code == 127 {
      return true
    }
  }
  false
}

///|
fn byte_len(input : String) -> Int {
  @utf8.encode(input).length()
}

///|
fn count_char(input : String, target : Char) -> Int {
  let mut count = 0
  for ch in input {
    if ch == target {
      count = count + 1
    }
  }
  count
}

///|
fn join_strings(items : Array[String], sep : String) -> String {
  let sb = StringBuilder::new()
  for i, item in items {
    if i > 0 {
      sb.write_string(sep)
    }
    sb.write_string(item)
  }
  sb.to_string()
}

///|
fn array_contains_string(items : Array[String], value : String) -> Bool {
  for item in items {
    if item == value {
      return true
    }
  }
  false
}

///|
fn push_unique_string(items : Array[String], value : String) -> Bool {
  if array_contains_string(items, value) {
    false
  } else {
    items.push(value)
    true
  }
}

///|
pub fn json_string(input : String) -> String {
  let sb = StringBuilder::new()
  for ch in input {
    if ch == '"' {
      sb.write_string("\\\"")
    } else if ch == '\\' {
      sb.write_string("\\\\")
    } else if ch == '\n' {
      sb.write_string("\\n")
    } else if ch == '\r' {
      sb.write_string("\\r")
    } else if ch == '\t' {
      sb.write_string("\\t")
    } else {
      sb.write_char(ch)
    }
  }
  sb.to_string()
}