///|
priv struct Reader {
  bytes : Bytes
  mut pos : Int32
  end : Int32
  big : Bool
  limits : Limits
}

///|
fn Reader::new(
  bytes : Bytes,
  start : Int32,
  end : Int32,
  big : Bool,
  limits : Limits,
) -> Reader raise TdmsError {
  demand(
    start >= 0 && start <= end && end <= bytes.length(),
    "invalid byte extent",
  )
  { bytes, pos: start, end, big, limits, }
}

///|
fn Reader::take(self : Reader, size : Int32) -> Bytes raise TdmsError {
  demand(size >= 0 && size <= self.end - self.pos, "truncated payload")
  let start = self.pos
  self.pos += size
  Bytes::makei(size, i => self.bytes[start + i])
}

///|
fn Reader::uint(self : Reader, width : Int32) -> UInt64 raise TdmsError {
  demand(
    width >= 1 && width <= 8 && width <= self.end - self.pos,
    "truncated integer",
  )
  let mut value = 0UL
  for i = 0; i < width; i = i + 1 {
    let shift = if self.big { (width - i - 1) * 8 } else { i * 8 }
    value = value | (self.bytes[self.pos + i].to_uint64() << shift)
  }
  self.pos += width
  value
}

///|
fn Reader::u32(self : Reader) -> UInt32 raise TdmsError {
  self.uint(4).to_uint()
}

///|
fn Reader::bounded(
  self : Reader,
  width : Int32,
  bound : Int32,
  label : String,
) -> Int32 raise TdmsError {
  let n = self.uint(width)
  if n > bound.to_uint64() {
    raise Budget(label)
  }
  n.to_int()
}

///|
fn Reader::signed(self : Reader, width : Int32) -> Int64 raise TdmsError {
  let n = self.uint(width)
  if width == 8 {
    return n.reinterpret_as_int64()
  }
  let sign = 1UL << (width * 8 - 1)
  if (n & sign) == 0UL {
    n.reinterpret_as_int64()
  } else {
    n.reinterpret_as_int64() - (1L << (width * 8))
  }
}

///|
fn Reader::string(self : Reader) -> String raise TdmsError {
  let size = self.bounded(4, self.limits.max_string_bytes, "string bytes")
  let b = self.take(size)
  @utf8.decode(b) catch {
    _ => raise Invalid("invalid UTF-8")
  }
}

///|
priv struct Writer {
  bytes : Array[Byte]
  big : Bool
  limits : Limits
}

///|
fn Writer::new(big : Bool, limits : Limits) -> Writer {
  { bytes: [], big, limits, }
}

///|
fn Writer::room(self : Writer, n : Int32) -> Unit raise TdmsError {
  if n < 0 || self.bytes.length() > self.limits.max_bytes - n {
    raise Budget("output bytes")
  }
}

///|
fn Writer::uint(
  self : Writer,
  value : UInt64,
  width : Int32,
) -> Unit raise TdmsError {
  self.room(width)
  for i = 0; i < width; i = i + 1 {
    let shift = if self.big { (width - i - 1) * 8 } else { i * 8 }
    self.bytes.push(((value >> shift) & 255UL).to_byte())
  }
}

///|
fn Writer::append(self : Writer, bytes : Bytes) -> Unit raise TdmsError {
  self.room(bytes.length())
  for b in bytes {
    self.bytes.push(b)
  }
}

///|
fn Writer::finish(self : Writer) -> Bytes {
  Bytes::makei(self.bytes.length(), i => self.bytes[i])
}

///|
fn Writer::string(self : Writer, value : String) -> Unit raise TdmsError {
  let bytes = @utf8.encode(value)
  if bytes.length() > self.limits.max_string_bytes {
    raise Budget("string bytes")
  }
  self.uint(bytes.length().to_uint64(), 4)
  self.append(bytes)
}

///|
fn checked_product(
  a : Int32,
  b : Int32,
  bound : Int32,
) -> Int32 raise TdmsError {
  demand(a >= 0 && b >= 0 && bound >= 0, "negative extent")
  if a > 0 && b > bound / a {
    raise Budget("extent product")
  }
  a * b
}

///|
pub fn decode_hex(
  text : String,
  limits~ : Limits = Limits::default(),
) -> Bytes raise TdmsError {
  limits.validate()
  demand(text.length() % 2 == 0, "odd hexadecimal length")
  if text.length() / 2 > limits.max_bytes {
    raise Budget("hexadecimal bytes")
  }
  let out : Array[Byte] = []
  let mut high = 0
  for i = 0; i < text.length(); i = i + 1 {
    let c = text.code_unit_at(i).to_int()
    let n = if c >= 48 && c <= 57 {
      c - 48
    } else if c >= 65 && c <= 70 {
      c - 55
    } else if c >= 97 && c <= 102 {
      c - 87
    } else {
      raise Invalid("invalid hexadecimal character")
    }
    if i % 2 == 0 {
      high = n
    } else {
      out.push((high * 16 + n).to_byte())
    }
  }
  Bytes::makei(out.length(), i => out[i])
}

///|
pub fn encode_hex(bytes : Bytes) -> String {
  let digits : Array[Char] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
  ]
  let out : Array[Char] = []
  for b in bytes {
    out.push(digits[b.to_int() / 16])
    out.push(digits[b.to_int() % 16])
  }
  String::from_array(out)
}