///|
/// Convert a hexadecimal nibble to a display character.
pub fn hex_digit(value : Int) -> Char {
  let normalized = value & 0xf
  if normalized < 10 {
    ('0'.to_int() + normalized).to_char().unwrap()
  } else {
    ('a'.to_int() + normalized - 10).to_char().unwrap()
  }
}

///|
pub fn hex_encode(data : Bytes) -> String {
  let out = StringBuilder()
  for byte in data {
    out.write_char(hex_digit(byte.to_int() >> 4))
    out.write_char(hex_digit(byte.to_int()))
  }
  out.to_string()
}

///|
pub fn hex_value(value : Char) -> Int? {
  match value {
    '0'..='9' => Some(value.to_int() - '0'.to_int())
    'a'..='f' => Some(value.to_int() - 'a'.to_int() + 10)
    'A'..='F' => Some(value.to_int() - 'A'.to_int() + 10)
    _ => None
  }
}

///|
pub fn hex_decode(text : String) -> Result[Bytes, String] {
  let chars : Array[Char] = []
  for char in text {
    chars.push(char)
  }
  if chars.length() % 2 != 0 {
    Err("hexadecimal input must contain an even number of digits")
  } else {
    let out : Array[Byte] = []
    for index in 0..<(chars.length() / 2) {
      match (hex_value(chars[index * 2]), hex_value(chars[index * 2 + 1])) {
        (Some(high), Some(low)) => out.push(((high << 4) | low).to_byte())
        (_, _) => return Err("hexadecimal input contains an invalid digit")
      }
    }
    Ok(Bytes::from_array(out))
  }
}

///|
pub fn bytes_equal_left(a : Bytes, b : Bytes) -> Bool {
  if a.length() != b.length() {
    false
  } else {
    let mut equal = true
    for index in 0.. Byte {
  let mut checksum : Int = 0
  for byte in data {
    checksum = checksum ^ byte.to_int()
  }
  checksum.to_byte()
}

///|
pub fn append_u24(out : Array[Byte], value : Int) -> Result[Unit, String] {
  if value < 0 || value > 0xffffff {
    Err("24-bit value is outside the protocol range")
  } else {
    out.push((value & 0xff).to_byte())
    out.push(((value >> 8) & 0xff).to_byte())
    out.push(((value >> 16) & 0xff).to_byte())
    Ok(())
  }
}

///|
pub fn read_u24(data : Bytes, offset : Int) -> Result[Int, String] {
  if offset < 0 || data.length() < offset + 3 {
    Err("24-bit value is truncated")
  } else {
    Ok(
      data[offset].to_int() |
      (data[offset + 1].to_int() << 8) |
      (data[offset + 2].to_int() << 16),
    )
  }
}

///|
/// A bounds-checked cursor for binary protocol decoders.
pub struct ByteCursor {
  data : Bytes
  mut offset : Int
} derive(Debug)

///|
pub fn ByteCursor::new(data : Bytes) -> ByteCursor {
  { data, offset: 0 }
}

///|
pub fn ByteCursor::offset(self : ByteCursor) -> Int {
  self.offset
}

///|
pub fn ByteCursor::remaining(self : ByteCursor) -> Int {
  self.data.length() - self.offset
}

///|
pub fn ByteCursor::done(self : ByteCursor) -> Bool {
  self.offset == self.data.length()
}

///|
pub fn ByteCursor::read_byte(self : ByteCursor) -> Result[Byte, Diagnostic] {
  if self.offset >= self.data.length() {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor reached the end of input",
        offset=self.offset,
      ),
    )
  } else {
    let value = self.data[self.offset]
    self.offset += 1
    Ok(value)
  }
}

///|
pub fn ByteCursor::read_u16(self : ByteCursor) -> Result[Int, Diagnostic] {
  if self.remaining() < 2 {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor cannot read a 16-bit value",
        offset=self.offset,
      ),
    )
  } else {
    let value = self.data[self.offset].to_int() |
      (self.data[self.offset + 1].to_int() << 8)
    self.offset += 2
    Ok(value)
  }
}

///|
pub fn ByteCursor::read_u24(self : ByteCursor) -> Result[Int, Diagnostic] {
  if self.remaining() < 3 {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor cannot read a 24-bit value",
        offset=self.offset,
      ),
    )
  } else {
    let value = self.data[self.offset].to_int() |
      (self.data[self.offset + 1].to_int() << 8) |
      (self.data[self.offset + 2].to_int() << 16)
    self.offset += 3
    Ok(value)
  }
}

///|
pub fn ByteCursor::read_u32(self : ByteCursor) -> Result[UInt, Diagnostic] {
  if self.remaining() < 4 {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor cannot read a 32-bit value",
        offset=self.offset,
      ),
    )
  } else {
    let value = self.data[self.offset].to_int().reinterpret_as_uint() |
      (self.data[self.offset + 1].to_int().reinterpret_as_uint() << 8) |
      (self.data[self.offset + 2].to_int().reinterpret_as_uint() << 16) |
      (self.data[self.offset + 3].to_int().reinterpret_as_uint() << 24)
    self.offset += 4
    Ok(value)
  }
}

///|
pub fn ByteCursor::read_bytes(
  self : ByteCursor,
  length : Int,
) -> Result[Bytes, Diagnostic] {
  if length < 0 || self.remaining() < length {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor cannot read the requested byte span",
        offset=self.offset,
      ),
    )
  } else {
    let value = self.data[self.offset:self.offset + length].to_owned()
    self.offset += length
    Ok(value)
  }
}

///|
pub fn ByteCursor::skip(
  self : ByteCursor,
  length : Int,
) -> Result[Unit, Diagnostic] {
  if length < 0 || self.remaining() < length {
    Err(
      Diagnostic::new(
        MalformedFrame,
        "cursor cannot skip the requested span",
        offset=self.offset,
      ),
    )
  } else {
    self.offset += length
    Ok(())
  }
}

///|
pub fn ByteCursor::seek(
  self : ByteCursor,
  offset : Int,
) -> Result[Unit, Diagnostic] {
  if offset < 0 || offset > self.data.length() {
    Err(
      Diagnostic::new(MalformedFrame, "cursor seek is outside input", offset~),
    )
  } else {
    self.offset = offset
    Ok(())
  }
}

///|
/// A small writer used by application-specific extensions.
pub struct ByteWriter {
  bytes : Array[Byte]
} derive(Debug)

///|
pub fn ByteWriter::new() -> ByteWriter {
  { bytes: [] }
}

///|
pub fn ByteWriter::len(self : ByteWriter) -> Int {
  self.bytes.length()
}

///|
pub fn ByteWriter::push(self : ByteWriter, value : Byte) -> Unit {
  self.bytes.push(value)
}

///|
pub fn ByteWriter::push_u16(
  self : ByteWriter,
  value : Int,
) -> Result[Unit, String] {
  if value < 0 || value > 65535 {
    Err("writer 16-bit value is outside range")
  } else {
    self.bytes.push((value & 0xff).to_byte())
    self.bytes.push(((value >> 8) & 0xff).to_byte())
    Ok(())
  }
}

///|
pub fn ByteWriter::push_u24(
  self : ByteWriter,
  value : Int,
) -> Result[Unit, String] {
  append_u24(self.bytes, value)
}

///|
pub fn ByteWriter::push_u32(self : ByteWriter, value : UInt) -> Unit {
  self.bytes.push((value & 0xffU).to_byte())
  self.bytes.push(((value >> 8) & 0xffU).to_byte())
  self.bytes.push(((value >> 16) & 0xffU).to_byte())
  self.bytes.push(((value >> 24) & 0xffU).to_byte())
}

///|
pub fn ByteWriter::push_bytes(self : ByteWriter, value : Bytes) -> Unit {
  for byte in value {
    self.bytes.push(byte)
  }
}

///|
pub fn ByteWriter::to_bytes(self : ByteWriter) -> Bytes {
  Bytes::from_array(self.bytes)
}

///|
/// Split a byte stream into complete APDUs and retain incomplete tails.
pub fn split_apdus(data : Bytes) -> Result[(Array[Frame], Bytes), Diagnostic] {
  let frames : Array[Frame] = []
  let mut offset = 0
  while offset < data.length() {
    match parse_apdu_prefix(data[offset:].to_owned()) {
      Complete(frame, consumed) => {
        frames.push(frame)
        offset += consumed
      }
      NeedMore(_) => return Ok((frames, data[offset:].to_owned()))
      Invalid(error) => return Err(error)
    }
  }
  Ok((frames, b""))
}

///|
pub fn pad_bytes(
  data : Bytes,
  length : Int,
  fill : Byte,
) -> Result[Bytes, String] {
  if length < data.length() {
    Err("padding length is shorter than input")
  } else {
    let out = data.to_array()
    while out.length() < length {
      out.push(fill)
    }
    Ok(Bytes::from_array(out))
  }
}

///|
pub fn trim_trailing_bytes(data : Bytes, value : Byte) -> Bytes {
  let mut end = data.length()
  while end > 0 && data[end - 1] == value {
    end -= 1
  }
  data[:end].to_owned()
}

///|
pub fn bit_is_set(value : Int, bit : Int) -> Bool {
  bit >= 0 && bit < 31 && (value & (1 << bit)) != 0
}

///|
pub fn set_bit(value : Int, bit : Int, enabled : Bool) -> Int {
  if bit < 0 || bit >= 31 {
    value
  } else if enabled {
    value | (1 << bit)
  } else {
    value & (0x7fffffff ^ (1 << bit))
  }
}

///|
pub fn mask_bits(value : Int, mask : Int) -> Int {
  value & mask
}

///|
pub fn wire_tool_examples() -> Array[Bytes] {
  [
    hex_decode("680401000000").unwrap(),
    trim_trailing_bytes(b"abc\x00\x00", b'\x00'),
  ]
}