///|
/// LSB-first bit reader used by the Brotli decoder.
///
/// The accumulator is 64-bit so a single 4-byte load refills enough bits for
/// several Huffman symbols. Callers never request more than 24 bits at once,
/// so whenever a refill is needed (`bits_avail < n <= 24`) there is always
/// room for a whole 32-bit load (`bits_avail <= 23`, `23 + 32 = 55 < 64`).
priv struct BrotliBitReader {
  buf : FixedArray[Byte]
  mut byte_pos : Int
  byte_end : Int
  mut bit_buf : UInt64
  mut bits_avail : Int
}

///|
fn BrotliBitReader::new(
  buf : FixedArray[Byte],
  offset : Int,
  length : Int,
) -> BrotliBitReader {
  {
    buf,
    byte_pos: offset,
    byte_end: offset + length,
    bit_buf: 0UL,
    bits_avail: 0,
  }
}

///|
fn BrotliBitReader::clone(self : BrotliBitReader) -> BrotliBitReader {
  {
    buf: self.buf,
    byte_pos: self.byte_pos,
    byte_end: self.byte_end,
    bit_buf: self.bit_buf,
    bits_avail: self.bits_avail,
  }
}

///|
fn BrotliBitReader::with_buffer(
  self : BrotliBitReader,
  buf : FixedArray[Byte],
) -> BrotliBitReader {
  {
    buf,
    byte_pos: self.byte_pos,
    byte_end: buf.length(),
    bit_buf: self.bit_buf,
    bits_avail: self.bits_avail,
  }
}

///|
fn BrotliBitReader::refill_to(
  self : BrotliBitReader,
  n : Int,
) -> Unit raise @common.FbrError {
  if self.bits_avail >= n {
    return
  }
  // Fast path: every caller requests at most 24 bits, so on entry
  // `bits_avail <= 23` and the 64-bit accumulator always has room for a
  // whole 32-bit load. One 4-byte load refills enough bits for several
  // Huffman symbols.
  if self.byte_pos + 3 < self.byte_end {
    let p = self.byte_pos
    let buf = self.buf
    let word = buf[p].to_uint() |
      (buf[p + 1].to_uint() << 8) |
      (buf[p + 2].to_uint() << 16) |
      (buf[p + 3].to_uint() << 24)
    self.bit_buf = self.bit_buf | (word.to_uint64() << self.bits_avail)
    self.byte_pos = p + 4
    self.bits_avail += 32
    return
  }
  while self.bits_avail < n {
    if self.byte_pos >= self.byte_end {
      raise @common.fbr_err(
        UnexpectedEOF,
        msg="brotli input ended while reading bits need=\{n} available=\{self.bits_avail}",
      )
    }
    self.bit_buf = self.bit_buf |
      (self.buf[self.byte_pos].to_uint().to_uint64() << self.bits_avail)
    self.byte_pos += 1
    self.bits_avail += 8
  }
}

///|
/// Pre-computed low-`n` bit masks indexed by `n` (0..=32). Allows
/// `brotli_low_mask(n)` to be a single array load instead of a branched
/// shift, which removes a measurable slice from the Huffman hot path.
let brotli_low_mask_lut : FixedArray[UInt] = [
  0U, 0x1U, 0x3U, 0x7U, 0xfU, 0x1fU, 0x3fU, 0x7fU, 0xffU, 0x1ffU, 0x3ffU, 0x7ffU,
  0xfffU, 0x1fffU, 0x3fffU, 0x7fffU, 0xffffU, 0x1ffffU, 0x3ffffU, 0x7ffffU, 0xfffffU,
  0x1fffffU, 0x3fffffU, 0x7fffffU, 0xffffffU, 0x1ffffffU, 0x3ffffffU, 0x7ffffffU,
  0xfffffffU, 0x1fffffffU, 0x3fffffffU, 0x7fffffffU, 0xffffffffU,
]

///|
fn brotli_low_mask(n : Int) -> UInt {
  brotli_low_mask_lut[n]
}

///|
fn BrotliBitReader::peek_bits(
  self : BrotliBitReader,
  n : Int,
) -> UInt raise @common.FbrError {
  if n < 0 || n > 24 {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="invalid Brotli bit read width",
    )
  }
  self.refill_to(n)
  self.bit_buf.to_uint() & brotli_low_mask(n)
}

///|
fn BrotliBitReader::drop_bits(
  self : BrotliBitReader,
  n : Int,
) -> Unit raise @common.FbrError {
  if n < 0 || n > self.bits_avail {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="invalid Brotli bit drop width",
    )
  }
  self.bit_buf = self.bit_buf >> n
  self.bits_avail -= n
}

///|
fn BrotliBitReader::take_bits(
  self : BrotliBitReader,
  n : Int,
) -> UInt raise @common.FbrError {
  let value = self.peek_bits(n)
  self.drop_bits(n)
  value
}

///|
fn BrotliBitReader::align_to_byte(
  self : BrotliBitReader,
) -> Unit raise @common.FbrError {
  // bits_avail is always non-negative; `bits_avail & 7` matches the number of
  // padding bits we must consume to land on the next byte boundary (because
  // every refill loads whole bytes and starts byte-aligned).
  let extra = self.bits_avail & 7
  if extra > 0 {
    let padding = self.take_bits(extra)
    if padding != 0U {
      raise @common.fbr_err(BrotliInvalidPadding)
    }
  }
}

///|
fn BrotliBitReader::take_bytes(
  self : BrotliBitReader,
  out : FixedArray[Byte],
  offset : Int,
  length : Int,
) -> Unit raise @common.FbrError {
  self.align_to_byte()
  if offset < 0 || length < 0 || offset > out.length() - length {
    raise @common.fbr_err(InvalidZipData, msg="output buffer too small")
  }
  let mut dst = offset
  let end = offset + length
  while dst < end && self.bits_avail >= 8 {
    out[dst] = (self.bit_buf.to_uint() & 0xffU).to_byte()
    self.bit_buf = self.bit_buf >> 8
    self.bits_avail -= 8
    dst += 1
  }
  let remaining = end - dst
  if remaining > self.byte_end - self.byte_pos {
    raise @common.fbr_err(
      UnexpectedEOF,
      msg="brotli input ended while reading bytes",
    )
  }
  if remaining > 0 {
    self.buf.blit_to(
      out,
      len=remaining,
      src_offset=self.byte_pos,
      dst_offset=dst,
    )
    self.byte_pos += remaining
  }
}

///|
fn BrotliBitReader::expect_final_padding_zero(
  self : BrotliBitReader,
) -> Unit raise @common.FbrError {
  if self.bits_avail > 0 {
    // bits_avail never exceeds 55 (a 32-bit refill only happens when
    // bits_avail <= 23), so the shift below is always in range.
    let mask = (1UL << self.bits_avail) - 1UL
    if (self.bit_buf & mask) != 0UL {
      raise @common.fbr_err(BrotliInvalidPadding)
    }
  }
  while self.byte_pos < self.byte_end {
    if self.buf[self.byte_pos] != b'\x00' {
      raise @common.fbr_err(BrotliInvalidPadding)
    }
    self.byte_pos += 1
  }
}