///|
fn char_at(s : String, index : Int) -> Char {
  s.get_char(index).unwrap()
}

///|
fn is_ctl(c : Char) -> Bool {
  let n = c.to_int()
  (n >= 0 && n <= 31) || n == 127
}

///|
fn is_ows(c : Char) -> Bool {
  c == ' ' || c == '\t'
}

///|
fn is_separator(c : Char) -> Bool {
  c == '(' ||
  c == ')' ||
  c == '<' ||
  c == '>' ||
  c == '@' ||
  c == ',' ||
  c == ';' ||
  c == ':' ||
  c == '\\' ||
  c == '"' ||
  c == '/' ||
  c == '[' ||
  c == ']' ||
  c == '?' ||
  c == '=' ||
  c == '{' ||
  c == '}' ||
  c == ' ' ||
  c == '\t'
}

///|
pub fn is_token_char(c : Char) -> Bool {
  c.is_ascii() && !is_ctl(c) && !is_separator(c)
}

///|
pub fn is_token(value : String) -> Bool {
  !value.is_empty() && value.all(c => is_token_char(c))
}

///|
fn is_attr_char(c : Char) -> Bool {
  c.is_ascii_alphabetic() ||
  c.is_ascii_digit() ||
  c == '!' ||
  c == '#' ||
  c == '$' ||
  c == '&' ||
  c == '+' ||
  c == '-' ||
  c == '.' ||
  c == '^' ||
  c == '_' ||
  c == '`' ||
  c == '|' ||
  c == '~'
}

///|
fn is_hex(c : Char) -> Bool {
  c.is_ascii_hexdigit()
}

///|
fn hex_value(c : Char) -> Int {
  if c >= '0' && c <= '9' {
    c.to_int() - '0'.to_int()
  } else if c >= 'A' && c <= 'F' {
    c.to_int() - 'A'.to_int() + 10
  } else if c >= 'a' && c <= 'f' {
    c.to_int() - 'a'.to_int() + 10
  } else {
    -1
  }
}

///|
fn is_ascii_visible(c : Char) -> Bool {
  c.is_ascii() && c.to_int() >= 32 && c.to_int() <= 126
}

///|
fn needs_quoted(value : String) -> Bool {
  value.is_empty() || !value.all(c => is_token_char(c))
}

///|
fn needs_extended_filename(value : String) -> Bool {
  value.any(c => !is_attr_char(c))
}

///|
fn append_percent_hex(buf : StringBuilder, byte : Int) -> Unit {
  let upper = "0123456789ABCDEF"
  buf.write_char('%')
  buf.write_char(char_at(upper, (byte >> 4) & 15))
  buf.write_char(char_at(upper, byte & 15))
}

///|
fn ascii_fallback_char(c : Char) -> Char {
  if is_ascii_visible(c) && c != '"' && c != '\\' && c != '/' {
    c
  } else {
    '_'
  }
}

///|
fn ascii_fallback(value : String) -> String {
  let buf = StringBuilder(size_hint=value.length())
  for c in value.iter() {
    buf.write_char(ascii_fallback_char(c))
  }
  buf.to_string()
}