///|
fn clean(input : StringView) -> String {
  input.trim().to_owned()
}

///|
fn split_quoted(input : String, sep : Char) -> Array[String] {
  let parts : Array[String] = []
  let buf = StringBuilder()
  let mut quoted = false
  let mut escaped = false
  for c in input.iter() {
    if escaped {
      buf.write_char(c)
      escaped = false
    } else if quoted && c == '\\' {
      buf.write_char(c)
      escaped = true
    } else if c == '"' {
      quoted = !quoted
      buf.write_char(c)
    } else if !quoted && c == sep {
      parts.push(buf.to_string())
      buf.reset()
    } else {
      buf.write_char(c)
    }
  }
  parts.push(buf.to_string())
  parts
}

///|
fn is_ascii_alpha(c : Char) -> Bool {
  let code = c.to_int()
  (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
}

///|
fn is_ascii_digit(c : Char) -> Bool {
  let code = c.to_int()
  code >= 48 && code <= 57
}

///|
fn digit_value(c : Char) -> Int? {
  let code = c.to_int()
  if code >= 48 && code <= 57 {
    Some(code - 48)
  } else {
    None
  }
}

///|
fn is_tchar(c : Char) -> Bool {
  let code = c.to_int()
  is_ascii_alpha(c) ||
  is_ascii_digit(c) ||
  code == 33 ||
  code == 35 ||
  code == 36 ||
  code == 37 ||
  code == 38 ||
  code == 39 ||
  code == 42 ||
  code == 43 ||
  code == 45 ||
  code == 46 ||
  code == 94 ||
  code == 95 ||
  code == 96 ||
  code == 124 ||
  code == 126
}

///|
fn is_token(input : String) -> Bool {
  if input.is_empty() {
    return false
  }
  for c in input.iter() {
    if !is_tchar(c) {
      return false
    }
  }
  true
}

///|
fn is_language_subtag(input : String) -> Bool {
  let len = input.length()
  if len < 1 || len > 8 {
    return false
  }
  for c in input.iter() {
    if !(is_ascii_alpha(c) || is_ascii_digit(c)) {
      return false
    }
  }
  true
}

///|
fn unquote(input : String) -> String {
  let len = input.length()
  if len >= 2 &&
    input.get_char(0) == Some('"') &&
    input.get_char(len - 1) == Some('"') {
    let inner = input[1:len - 1].to_owned()
    let out = StringBuilder(size_hint=inner.length())
    let mut escaped = false
    for c in inner.iter() {
      if escaped {
        out.write_char(c)
        escaped = false
      } else if c == '\\' {
        escaped = true
      } else {
        out.write_char(c)
      }
    }
    if escaped {
      out.write_char('\\')
    }
    out.to_string()
  } else {
    input
  }
}

///|
fn parse_param(raw : String, item : Int) -> Result[Param, ParseError] {
  let piece = clean(raw[:])
  match piece.split_once("=") {
    Some((name_view, value_view)) => {
      let name = clean(name_view).to_lower()
      let value = unquote(clean(value_view))
      if !is_token(name) {
        Err(InvalidToken(item, "parameter name", name))
      } else {
        Ok({ name, value })
      }
    }
    None => Err(InvalidParameter(item, piece))
  }
}

///|
fn parse_q(raw : String, item : Int) -> Result[Int, ParseError] {
  let q = clean(raw[:])
  if q == "1" {
    return Ok(1000)
  }
  if q == "0" {
    return Ok(0)
  }
  if q.length() >= 2 && q.get_char(1) == Some('.') {
    match q.get_char(0) {
      Some('0') => parse_q_fraction(q, item, false)
      Some('1') => parse_q_fraction(q, item, true)
      _ => Err(InvalidQ(item, q))
    }
  } else {
    Err(InvalidQ(item, q))
  }
}

///|
fn parse_q_fraction(
  q : String,
  item : Int,
  is_one : Bool,
) -> Result[Int, ParseError] {
  let mut value = 0
  let mut digits = 0
  for i in 2..
        match digit_value(c) {
          Some(d) => {
            digits += 1
            if digits > 3 {
              return Err(InvalidQ(item, q))
            }
            if is_one && d != 0 {
              return Err(InvalidQ(item, q))
            }
            value = value * 10 + d
          }
          None => return Err(InvalidQ(item, q))
        }
      None => return Err(InvalidQ(item, q))
    }
  }
  while digits < 3 {
    value = value * 10
    digits += 1
  }
  if is_one {
    Ok(1000)
  } else {
    Ok(value)
  }
}

///|
pub fn quality_to_string(q : Int) -> String {
  let clamped = if q < 0 { 0 } else if q > 1000 { 1000 } else { q }
  let whole = clamped / 1000
  let frac = clamped % 1000
  let buf = StringBuilder(size_hint=5)
  buf.write_string(whole.to_string())
  buf.write_char('.')
  if frac < 100 {
    buf.write_char('0')
  }
  if frac < 10 {
    buf.write_char('0')
  }
  buf.write_string(frac.to_string())
  buf.to_string()
}

///|
fn param_value(params : Array[Param], name : String) -> String? {
  let wanted = name.to_lower()
  for p in params {
    if p.name == wanted {
      return Some(p.value)
    }
  }
  None
}

///|
fn params_match(needed : Array[Param], offered : Array[Param]) -> Bool {
  for p in needed {
    match param_value(offered, p.name) {
      Some(value) => if value != p.value { return false }
      None => return false
    }
  }
  true
}

///|
fn append_params(buf : StringBuilder, params : Array[Param]) -> Unit {
  for p in params {
    buf.write_string(";")
    buf.write_string(p.name)
    buf.write_string("=")
    buf.write_string(p.value)
  }
}