///|
fn read_bits(
  bit_count : Int,
  data_val : Ref[Int],
  data_position : Ref[Int],
  data_index : Ref[Int],
  reset_value : Int,
  get_next_value : (Int) -> Int,
) -> Int {
  let mut bits = 0
  let mut power = 1
  let maxpower = 1 << bit_count
  while power != maxpower {
    let resb = data_val.val & data_position.val
    data_position.val = data_position.val >> 1
    if data_position.val == 0 {
      data_position.val = reset_value
      data_val.val = get_next_value(data_index.val)
      data_index.val += 1
    }
    bits = bits | ((if resb > 0 { 1 } else { 0 }) * power)
    power = power << 1
  }
  bits
}

///|
fn read_new_entry(
  data_val : Ref[Int],
  data_position : Ref[Int],
  data_index : Ref[Int],
  reset_value : Int,
  get_next_value : (Int) -> Int,
) -> String? {
  let mode = read_bits(
    2, data_val, data_position, data_index, reset_value, get_next_value,
  )
  match mode {
    0 =>
      Some(
        read_bits(
          8, data_val, data_position, data_index, reset_value, get_next_value,
        )
        |> char_from_code_unit,
      )
    1 =>
      Some(
        read_bits(
          16, data_val, data_position, data_index, reset_value, get_next_value,
        )
        |> char_from_code_unit,
      )
    2 => Some("")
    _ => None
  }
}

///|
fn _decompress(
  length : Int,
  reset_value : Int,
  get_next_value : (Int) -> Int,
) -> String? {
  let dictionary : Array[String] = ["0", "1", "2"]
  let result = StringBuilder::new()
  let data_val = Ref::new(get_next_value(0))
  let data_position = Ref::new(reset_value)
  let data_index = Ref::new(1)

  let mut enlarge_in = 4
  let mut dict_size = 4
  let mut num_bits = 3

  let first = read_new_entry(
    data_val, data_position, data_index, reset_value, get_next_value,
  )
  let c = match first {
    Some(v) => v
    None => return Some("")
  }
  if c == "" {
    return Some("")
  }

  dictionary.push(c)
  let mut w = c
  result.write_string(c)

  while true {
    if data_index.val > length {
      return Some("")
    }

    let mut c_idx = read_bits(
      num_bits, data_val, data_position, data_index, reset_value, get_next_value,
    )

    if c_idx == 0 {
      let code = read_bits(
        8, data_val, data_position, data_index, reset_value, get_next_value,
      )
      dictionary.push(char_from_code_unit(code))
      c_idx = dict_size
      dict_size += 1
      enlarge_in -= 1
    } else if c_idx == 1 {
      let code = read_bits(
        16, data_val, data_position, data_index, reset_value, get_next_value,
      )
      dictionary.push(char_from_code_unit(code))
      c_idx = dict_size
      dict_size += 1
      enlarge_in -= 1
    } else if c_idx == 2 {
      return Some(result.to_string())
    }

    if enlarge_in == 0 {
      enlarge_in = 1 << num_bits
      num_bits += 1
    }

    let entry = if c_idx < dictionary.length() {
      dictionary[c_idx]
    } else if c_idx == dict_size {
      w + single_code_unit(w, 0)
    } else {
      return None
    }

    result.write_string(entry)
    dictionary.push(w + single_code_unit(entry, 0))
    dict_size += 1
    enlarge_in -= 1
    w = entry

    if enlarge_in == 0 {
      enlarge_in = 1 << num_bits
      num_bits += 1
    }
  }
  None
}