///|
const UPPER_A = 65

///|
const UPPER_Z = 90

///|
const LOWER_DELTA = 32

///|
fn char_code(s : String, i : Int) -> Int {
  s.unsafe_get(i).to_int()
}

///|
fn code_to_char(code : Int) -> Char {
  code.unsafe_to_char()
}

///|
fn is_space_code(code : Int) -> Bool {
  code == 32 || code == 9 || code == 10 || code == 13 || code == 12
}

///|
fn ascii_lower_code(code : Int) -> Int {
  if code >= UPPER_A && code <= UPPER_Z {
    code + LOWER_DELTA
  } else {
    code
  }
}

///|
fn trim_ascii(input : String) -> String {
  let len = input.length()
  let mut start = 0
  while start < len && is_space_code(char_code(input, start)) {
    start = start + 1
  }
  let mut end = len
  while end > start && is_space_code(char_code(input, end - 1)) {
    end = end - 1
  }
  input[start:end].to_owned()
}

///|
fn lower_ascii(input : String) -> String {
  let chars : Array[Char] = []
  for i in 0.. Bool {
  if offset < 0 || offset + prefix.length() > text.length() {
    return false
  }
  for i in 0.. Bool {
  starts_with_at(text, prefix, 0)
}

///|
fn ends_with(text : String, suffix : String) -> Bool {
  if suffix.length() > text.length() {
    false
  } else {
    starts_with_at(text, suffix, text.length() - suffix.length())
  }
}

///|
pub fn contains_text(text : String, needle : String) -> Bool {
  if needle.length() == 0 {
    return true
  }
  if needle.length() > text.length() {
    return false
  }
  let mut start = 0
  let last = text.length() - needle.length()
  while start <= last {
    if starts_with_at(text, needle, start) {
      return true
    }
    start = start + 1
  }
  false
}

///|
fn split_by_char(input : String, sep : Int) -> Array[String] {
  let out : Array[String] = []
  let mut start = 0
  let mut i = 0
  while i < input.length() {
    if char_code(input, i) == sep {
      out.push(input[start:i].to_owned())
      start = i + 1
    }
    i = i + 1
  }
  out.push(input[start:].to_owned())
  out
}

///|
fn split_words(input : String) -> Array[String] {
  let words : Array[String] = []
  let mut i = 0
  while i < input.length() {
    while i < input.length() && is_space_code(char_code(input, i)) {
      i = i + 1
    }
    let start = i
    while i < input.length() && !is_space_code(char_code(input, i)) {
      i = i + 1
    }
    if i > start {
      words.push(input[start:i].to_owned())
    }
  }
  words
}

///|
fn join_strings(items : Array[String], sep : String) -> String {
  let mut out = ""
  for i, item in items {
    if i > 0 {
      out = out + sep
    }
    out = out + item
  }
  out
}

///|
fn strip_quotes(input : String) -> String {
  let text = trim_ascii(input)
  if text.length() >= 2 &&
    char_code(text, 0) == 39 &&
    char_code(text, text.length() - 1) == 39 {
    text[1:text.length() - 1].to_owned()
  } else {
    text
  }
}

///|
fn normalize_token(input : String) -> String {
  lower_ascii(trim_ascii(input))
}

///|
fn normalize_url(input : String) -> String {
  trim_ascii(input)
}

///|
fn find_scheme_end(url : String) -> Int {
  let mut i = 0
  while i + 2 < url.length() {
    if char_code(url, i) == 58 &&
      char_code(url, i + 1) == 47 &&
      char_code(url, i + 2) == 47 {
      return i
    }
    i = i + 1
  }
  -1
}

///|
fn extract_origin(url : String) -> String {
  let scheme_end = find_scheme_end(url)
  if scheme_end < 0 {
    return ""
  }
  let mut end = scheme_end + 3
  while end < url.length() &&
        char_code(url, end) != 47 &&
        char_code(url, end) != 63 &&
        char_code(url, end) != 35 {
    end = end + 1
  }
  lower_ascii(url[:end].to_owned())
}

///|
fn extract_scheme(url : String) -> String {
  let scheme_end = find_scheme_end(url)
  if scheme_end < 0 {
    ""
  } else {
    lower_ascii(url[:scheme_end + 1].to_owned())
  }
}

///|
fn extract_host_pattern(source : String) -> String {
  let value = lower_ascii(source)
  let scheme_end = find_scheme_end(value)
  if scheme_end >= 0 {
    let mut end = scheme_end + 3
    while end < value.length() && char_code(value, end) != 47 {
      end = end + 1
    }
    value[scheme_end + 3:end].to_owned()
  } else {
    value
  }
}