///|
/// Range-checked little-endian primitives; counts are bounded before Int conversion.
priv struct Reader {
  data : Bytes
  mut pos : Int32
  end : Int32
}

///|
fn Reader::new(
  data : Bytes,
  start : Int32,
  end : Int32,
) -> Reader raise LasError {
  require(
    start >= 0 && end >= start && end <= data.length(),
    "invalid reader range",
  )
  { data, pos: start, end, }
}

///|
fn Reader::take(self : Reader, n : Int32) -> Bytes raise LasError {
  if n < 0 || n > self.end - self.pos {
    raise Truncated(self.pos)
  }
  let start = self.pos
  self.pos += n
  Bytes::from_array(Array::makei(n, i => self.data[start + i])[:])
}

///|
fn Reader::u8(self : Reader) -> Int32 raise LasError {
  if self.pos >= self.end {
    raise Truncated(self.pos)
  }
  let n = self.data[self.pos].to_int()
  self.pos += 1
  n
}

///|
fn Reader::u16(self : Reader) -> Int32 raise LasError {
  let a = self.u8()
  a | (self.u8() << 8)
}

///|
fn Reader::i16(self : Reader) -> Int32 raise LasError {
  let n = self.u16()
  if n >= 32768 {
    n - 65536
  } else {
    n
  }
}

///|
fn Reader::i32(self : Reader) -> Int32 raise LasError {
  let a = self.u16()
  a | (self.u16() << 16)
}

///|
fn Reader::u32(self : Reader, cap : Int32) -> Int32 raise LasError {
  let n = self.i32()
  if n < 0 || n > cap {
    raise Limit("unsigned count exceeds budget")
  }
  n
}

///|
fn Reader::u64(self : Reader, cap : Int32) -> Int32 raise LasError {
  let lo = self.u32(cap)
  let hi = self.i32()
  if hi != 0 {
    raise Limit("64-bit count exceeds supported budget")
  }
  lo
}

///|
fn Reader::f64(self : Reader) -> Double raise LasError {
  let mut bits = 0L
  for i in 0..<8 {
    bits = bits | (self.u8().to_int64() << (i * 8))
  }
  let v = bits.reinterpret_as_double()
  require(finite(v), "nonfinite floating point field")
  v
}

///|
fn Reader::text(self : Reader, width : Int32) -> String raise LasError {
  let data = self.take(width)
  let chars : Array[Char] = []
  let mut ended = false
  for b in data {
    let n = b.to_int()
    if n == 0 {
      ended = true
    }
    if !ended {
      require(n >= 32 && n <= 126, "metadata text must be printable ASCII")
      chars.push(n.unsafe_to_char())
    }
  }
  String::from_array(chars[:])
}

///|
/// Validates length arithmetic using division instead of overflowing multiplication.
fn checked_size(
  count : Int32,
  width : Int32,
  available : Int32,
) -> Unit raise LasError {
  require(count >= 0 && width > 0 && available >= 0, "invalid record span")
  if count > available / width {
    raise Truncated(available)
  }
}