///|
fn brotli_decode_var_len_uint8(
  reader : BrotliBitReader,
) -> Int raise @common.FbrError {
  if reader.take_bits(1) == 0U {
    0
  } else {
    let bits = reader.take_bits(3).reinterpret_as_int()
    if bits == 0 {
      1
    } else {
      (1 << bits) + reader.take_bits(bits).reinterpret_as_int()
    }
  }
}

///|
fn brotli_read_block_type_count(
  reader : BrotliBitReader,
) -> Int raise @common.FbrError {
  brotli_decode_var_len_uint8(reader) + 1
}

///|
fn brotli_read_block_length(
  reader : BrotliBitReader,
  table : FixedArray[Int],
) -> Int raise @common.FbrError {
  let code = brotli_read_symbol(
    reader, table, @common.brotli_huffman_table_bits,
  )
  if code < 0 || code >= @common.brotli_num_block_length_symbols {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="invalid Brotli block length code",
    )
  }
  let extra_bits = @common.brotli_block_length_extra_bits[code]
  @common.brotli_block_length_bases[code] +
  reader.take_bits(extra_bits).reinterpret_as_int()
}

///|
priv struct BrotliBlockTracker {
  num_types : Int
  mut current_type : Int
  mut previous_type_2 : Int
  mut previous_type_1 : Int
  mut remaining : Int
}

///|
fn BrotliBlockTracker::new_with_length(
  num_types : Int,
  initial_length : Int,
) -> BrotliBlockTracker raise @common.FbrError {
  if num_types < 1 {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="block type count must be positive",
    )
  }
  if initial_length < 1 {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="block length must be positive",
    )
  }
  {
    num_types,
    current_type: 0,
    previous_type_2: 1,
    previous_type_1: 0,
    remaining: initial_length,
  }
}

///|
fn BrotliBlockTracker::resolve_type_code(
  self : BrotliBlockTracker,
  code : Int,
) -> Int raise @common.FbrError {
  if self.num_types == 1 {
    self.current_type = 0
    return 0
  }
  let mut block_type = if code == 1 {
    self.previous_type_1 + 1
  } else if code == 0 {
    self.previous_type_2
  } else {
    code - 2
  }
  if block_type < 0 {
    raise @common.fbr_err(BrotliInvalidMetablock, msg="negative block type")
  }
  if block_type >= self.num_types {
    block_type -= self.num_types
  }
  self.previous_type_2 = self.previous_type_1
  self.previous_type_1 = block_type
  self.current_type = block_type
  block_type
}

///|
fn BrotliBlockTracker::switch_with_code(
  self : BrotliBlockTracker,
  code : Int,
  length : Int,
) -> Unit raise @common.FbrError {
  if length < 1 {
    raise @common.fbr_err(
      @common.BrotliInvalidMetablock,
      msg="block length must be positive",
    )
  }
  ignore(self.resolve_type_code(code))
  self.remaining = length
}

///|
fn BrotliBlockTracker::ensure_ready(
  self : BrotliBlockTracker,
  reader : BrotliBitReader,
  header : BrotliBlockSwitchHeader,
) -> Unit raise @common.FbrError {
  if self.remaining > 0 {
    return
  }
  if header.num_types == 1 {
    self.current_type = 0
    self.remaining = @common.brotli_block_size_cap
    return
  }
  guard header.type_tree is Some(type_tree) else {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="missing Brotli block type tree",
    )
  }
  guard header.length_tree is Some(length_tree) else {
    raise @common.fbr_err(
      BrotliInvalidMetablock,
      msg="missing Brotli block length tree",
    )
  }
  let type_code = brotli_read_symbol(
    reader, type_tree, @common.brotli_huffman_table_bits,
  )
  let length = brotli_read_block_length(reader, length_tree)
  self.switch_with_code(type_code, length)
}