///|
priv struct Reader {
  data : Bytes
  mut offset : Int
  mut limit : Int
}

///|
fn Reader::new(data : Bytes) -> Reader {
  { data, offset: 0, limit: data.length(), }
}

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

///|
fn Reader::require(self : Reader, count : Int) -> Unit raise IppError {
  if count < 0 || count > self.remaining() {
    raise Truncated(self.offset, count)
  }
  if count > self.limit - self.offset {
    raise Limit("message bytes")
  }
}

///|
fn Reader::byte(self : Reader) -> Int raise IppError {
  self.require(1)
  let value = self.data[self.offset].to_int()
  self.offset += 1
  value
}

///|
fn Reader::u16(self : Reader) -> Int raise IppError {
  let high = self.byte()
  let low = self.byte()
  (high << 8) | low
}

///|
fn Reader::i32(self : Reader) -> Int raise IppError {
  let a = self.byte()
  let b = self.byte()
  let c = self.byte()
  let d = self.byte()
  (a << 24) | (b << 16) | (c << 8) | d
}

///|
fn Reader::bytes(self : Reader, count : Int) -> Bytes raise IppError {
  self.require(count)
  let bytes = self.data
    .exact_view(start=self.offset, end=self.offset + count)
    .to_owned()
  self.offset += count
  bytes
}

///|
fn Reader::length_bytes(self : Reader) -> Bytes raise IppError {
  let count = self.u16()
  self.bytes(count)
}

///|
fn put_byte(output : @buffer.Buffer, value : Int) -> Unit raise IppError {
  if value < 0 || value > 255 {
    raise Invalid("byte out of range")
  }
  output.write_byte(value.to_byte())
}

///|
fn put_u16(output : @buffer.Buffer, value : Int) -> Unit raise IppError {
  if value < 0 || value > 65535 {
    raise Invalid("16-bit value out of range")
  }
  put_byte(output, (value >> 8) & 255)
  put_byte(output, value & 255)
}

///|
fn put_i32(output : @buffer.Buffer, value : Int) -> Unit raise IppError {
  put_byte(output, (value >> 24) & 255)
  put_byte(output, (value >> 16) & 255)
  put_byte(output, (value >> 8) & 255)
  put_byte(output, value & 255)
}

///|
fn put_length_bytes(
  output : @buffer.Buffer,
  bytes : Bytes,
) -> Unit raise IppError {
  put_u16(output, bytes.length())
  output.write_bytes(bytes)
}

///|
fn decode_utf8(bytes : Bytes) -> String raise IppError {
  @utf8.decode(bytes) catch {
    _ => raise Invalid("malformed UTF-8 in attribute")
  }
}

///|
fn encode_utf8(text : String) -> Bytes {
  @utf8.encode(text)
}

///|
pub fn bytes_to_hex(bytes : Bytes) -> String {
  let out = StringBuilder()
  let digits = "0123456789abcdef"
  for byte in bytes {
    let n = byte.to_int()
    out.write_char(digits[n >> 4].to_char().unwrap())
    out.write_char(digits[n & 15].to_char().unwrap())
  }
  out.to_string()
}

///|
fn hex_nibble(char : UInt16) -> Int? {
  let n = char.to_int()
  if n >= 48 && n <= 57 {
    Some(n - 48)
  } else if n >= 65 && n <= 70 {
    Some(n - 55)
  } else if n >= 97 && n <= 102 {
    Some(n - 87)
  } else {
    None
  }
}

///|
pub fn bytes_from_hex(text : String) -> Bytes raise IppError {
  if text.length() % 2 != 0 {
    raise Invalid("hex input has odd length")
  }
  let output = @buffer.Buffer()
  for i = 0; i < text.length(); i = i + 2 {
    let high = match hex_nibble(text[i]) {
      Some(value) => value
      None => raise Invalid("invalid hex digit at \{i}")
    }
    let low = match hex_nibble(text[i + 1]) {
      Some(value) => value
      None => raise Invalid("invalid hex digit at \{i + 1}")
    }
    put_byte(output, high * 16 + low)
  }
  output.to_bytes()
}