///|
/// Lowercase hex encoding of a byte view.
pub fn to_hex_lower(data : BytesView) -> String {
  let sb = StringBuilder()
  for b in data {
    let v = b.to_int()
    sb.write_char(lower_hex_digit((v >> 4) & 0xF))
    sb.write_char(lower_hex_digit(v & 0xF))
  }
  sb.to_string()
}

///|
/// Uppercase hex encoding of a byte view.
pub fn to_hex_upper(data : BytesView) -> String {
  let sb = StringBuilder()
  for b in data {
    let v = b.to_int()
    sb.write_char(upper_hex_digit((v >> 4) & 0xF))
    sb.write_char(upper_hex_digit(v & 0xF))
  }
  sb.to_string()
}

///|
fn lower_hex_digit(nibble : Int) -> Char {
  if nibble < 10 {
    ('0'.to_int() + nibble).unsafe_to_char()
  } else {
    ('a'.to_int() + nibble - 10).unsafe_to_char()
  }
}

///|
fn upper_hex_digit(nibble : Int) -> Char {
  if nibble < 10 {
    ('0'.to_int() + nibble).unsafe_to_char()
  } else {
    ('A'.to_int() + nibble - 10).unsafe_to_char()
  }
}

///|
/// Decode an even-length hexadecimal string into bytes.
pub fn from_hex(s : String) -> Result[Bytes, String] {
  let bs = @utf8.encode(s)
  let n = bs.length()
  if n % 2 != 0 {
    return Err("hex string has odd length")
  }
  let out : Array[Byte] = []
  let mut i = 0
  while i < n {
    let hi = hex_value_int(bs.get(i).unwrap().to_int())
    let lo = hex_value_int(bs.get(i + 1).unwrap().to_int())
    if hi < 0 || lo < 0 {
      return Err("invalid hex digit in: \{s}")
    }
    out.push(((hi << 4) | lo).to_byte())
    i = i + 2
  }
  Ok(Bytes::from_array(out))
}

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