///|
fn ascii_lower(input : String) -> String {
  let out = StringBuilder()
  for i = 0; i < input.length(); i = i + 1 {
    let c = input[i].to_int()
    out.write_char(
      Int::unsafe_to_char(if c >= 65 && c <= 90 { c + 32 } else { c }),
    )
  }
  out.to_string()
}

///|
fn is_lcalpha(c : Int) -> Bool {
  c >= 97 && c <= 122
}

///|
fn is_digit(c : Int) -> Bool {
  c >= 48 && c <= 57
}

///|
fn is_key_first(c : Int) -> Bool {
  is_lcalpha(c) || c == 42
}

///|
fn is_key_rest(c : Int) -> Bool {
  is_lcalpha(c) || is_digit(c) || c == 95 || c == 45 || c == 46 || c == 42
}

///|
fn hex_value(c : Int) -> Int {
  if c >= 48 && c <= 57 {
    c - 48
  } else if c >= 65 && c <= 70 {
    c - 55
  } else if c >= 97 && c <= 102 {
    c - 87
  } else {
    -1
  }
}

///|
fn hex_to_bytes(value : String) -> Bytes raise DigestFieldError {
  if value.length() % 2 != 0 {
    raise State("ODD_HEX_DIGEST")
  }
  let out : Array[Byte] = []
  for i = 0; i < value.length(); i = i + 2 {
    let hi = hex_value(value[i].to_int())
    let lo = hex_value(value[i + 1].to_int())
    if hi < 0 || lo < 0 {
      raise State("INVALID_HEX_DIGEST")
    }
    out.push((hi * 16 + lo).to_byte())
  }
  Bytes::from_array(out)
}

///|
fn bytes_equal_constant_time(left : Bytes, right : Bytes) -> Bool {
  let max_length = left.length().max(right.length())
  let mut difference = left.length() ^ right.length()
  for i = 0; i < max_length; i = i + 1 {
    let a = if i < left.length() { left[i].to_int() } else { 0 }
    let b = if i < right.length() { right[i].to_int() } else { 0 }
    difference = difference | (a ^ b)
  }
  difference == 0
}

///|
fn string_in(values : Array[String], expected : String) -> Bool {
  for value in values {
    if ascii_lower(value) == expected {
      return true
    }
  }
  false
}

///|
fn sort_digest_members(values : Array[DigestMember]) -> Array[DigestMember] {
  let out : Array[DigestMember] = []
  for value in values {
    out.push(value)
  }
  out.sort_by(fn(a, b) { String::lexical_compare(a.algorithm, b.algorithm) })
  out
}

///|
fn sort_preferences(values : Array[Preference]) -> Array[Preference] {
  let out : Array[Preference] = []
  for value in values {
    out.push(value)
  }
  out.sort_by(fn(a, b) { String::lexical_compare(a.algorithm, b.algorithm) })
  out
}