///|
fn pdf_lzw_next_code(
  bits : BitStream,
  code_length : Int,
) -> Int? raise PdfError {
  try bits.get_value_int(code_length) catch {
    EndOfInput => None
    _ => raise InvalidLZWData
  } noraise {
    value => Some(value)
  }
}

///|
fn pdf_lzw_decode_code(
  code : Int,
  prefix_code : Array[Int],
  append_character : Array[Int],
  next_code : Int,
) -> Array[Int] raise PdfError {
  if code < 0 || code >= next_code || code >= prefix_code.length() {
    raise InvalidLZWData
  }
  let stack : Array[Int] = Array::new(capacity=16)
  let mut current = code
  let mut depth = 0
  while current > 255 {
    if current >= next_code || current >= prefix_code.length() || depth > 8192 {
      raise InvalidLZWData
    }
    stack.push(append_character[current])
    current = prefix_code[current]
    depth += 1
  }
  if current < 0 || current > 255 {
    raise InvalidLZWData
  }
  let output : Array[Int] = Array::new(capacity=stack.length() + 1)
  output.push(current)
  if stack.length() > 0 {
    for offset in 0.. Int? raise PdfError {
  let mut code = 256
  while code == 256 {
    match pdf_lzw_next_code(bits, code_length) {
      None => break None
      Some(value) => code = value
    }
  } nobreak {
    Some(code)
  }
}

///|
fn pdf_lzw_output_literal(
  output : Array[Byte],
  code : Int,
) -> Unit raise PdfError {
  if code < 0 || code > 255 {
    raise InvalidLZWData
  }
  output.push(code.to_byte())
}

///|
fn pdf_lzw_consumed_bytes(bits : BitStream) -> Int {
  let count = bits.bits_read()
  if count % 8 == 0 {
    count / 8
  } else {
    count / 8 + 1
  }
}

///|
fn pdf_lzw_decode_view_prefix(
  data : BytesView,
  early_change? : Int = 1,
) -> (PdfBytes, Int) raise PdfError {
  let prefix_code = Array::make(8192, 0)
  let append_character = Array::make(8192, 0)
  let bits = bitstream_of_view(data)
  let output : Array[Byte] = Array::new(capacity=data.length())
  let mut code_length = 9
  let mut next_code = 258
  let mut old_code = 256
  let mut character = 0
  match pdf_lzw_read_non_clear_code(bits, code_length) {
    None => raise InvalidLZWData
    Some(257) => (Bytes::from_array(output), pdf_lzw_consumed_bytes(bits))
    Some(first_code) => {
      pdf_lzw_output_literal(output, first_code)
      old_code = first_code
      character = first_code
      let mut consumed = 0
      let mut finished = false
      while !finished {
        match pdf_lzw_next_code(bits, code_length) {
          None => raise InvalidLZWData
          Some(257) => {
            consumed = pdf_lzw_consumed_bytes(bits)
            finished = true
          }
          Some(256) => {
            code_length = 9
            next_code = 258
            old_code = 256
            match pdf_lzw_read_non_clear_code(bits, code_length) {
              None => raise InvalidLZWData
              Some(257) => {
                consumed = pdf_lzw_consumed_bytes(bits)
                finished = true
              }
              Some(code) => {
                pdf_lzw_output_literal(output, code)
                old_code = code
                character = code
              }
            }
          }
          Some(new_code) => {
            let chars = if new_code == next_code {
              let expanded = pdf_lzw_decode_code(
                old_code, prefix_code, append_character, next_code,
              )
              expanded.push(character)
              expanded
            } else if new_code < next_code {
              pdf_lzw_decode_code(
                new_code, prefix_code, append_character, next_code,
              )
            } else {
              raise InvalidLZWData
            }
            character = chars[0]
            for value in chars {
              pdf_lzw_output_literal(output, value)
            }
            if next_code < prefix_code.length() {
              prefix_code[next_code] = old_code
              append_character[next_code] = character
              next_code += 1
              match next_code + early_change {
                512 | 1024 | 2048 => code_length += 1
                _ => ()
              }
            }
            old_code = new_code
          }
        }
      }
      (Bytes::from_array(output), consumed)
    }
  }
}

///|
/// Decode PDF LZW data from a byte view.
///
/// `early_change` controls when code width grows; PDF streams default to `1`.
/// Malformed codes or bitstream errors raise `PdfError::InvalidLZWData`.
pub fn pdf_lzw_decode_view(
  data : BytesView,
  early_change? : Int = 1,
) -> PdfBytes raise PdfError {
  let prefix_code = Array::make(8192, 0)
  let append_character = Array::make(8192, 0)
  let bits = bitstream_of_view(data)
  let output : Array[Byte] = Array::new(capacity=data.length())
  let mut code_length = 9
  let mut next_code = 258
  let mut old_code = 256
  let mut character = 0
  match pdf_lzw_read_non_clear_code(bits, code_length) {
    None | Some(257) => Bytes::from_array(output)
    Some(first_code) => {
      pdf_lzw_output_literal(output, first_code)
      old_code = first_code
      character = first_code
      let mut finished = false
      while !finished {
        match pdf_lzw_next_code(bits, code_length) {
          None => finished = true
          Some(257) => finished = true
          Some(256) => {
            code_length = 9
            next_code = 258
            old_code = 256
            match pdf_lzw_read_non_clear_code(bits, code_length) {
              None | Some(257) => finished = true
              Some(code) => {
                pdf_lzw_output_literal(output, code)
                old_code = code
                character = code
              }
            }
          }
          Some(new_code) => {
            let chars = if new_code == next_code {
              let expanded = pdf_lzw_decode_code(
                old_code, prefix_code, append_character, next_code,
              )
              expanded.push(character)
              expanded
            } else if new_code < next_code {
              pdf_lzw_decode_code(
                new_code, prefix_code, append_character, next_code,
              )
            } else {
              raise InvalidLZWData
            }
            character = chars[0]
            for value in chars {
              pdf_lzw_output_literal(output, value)
            }
            if next_code < prefix_code.length() {
              prefix_code[next_code] = old_code
              append_character[next_code] = character
              next_code += 1
              match next_code + early_change {
                512 | 1024 | 2048 => code_length += 1
                _ => ()
              }
            }
            old_code = new_code
          }
        }
      }
      Bytes::from_array(output)
    }
  }
}

///|
/// Owned-byte wrapper for `pdf_lzw_decode_view`.
pub fn pdf_lzw_decode(
  data : PdfBytes,
  early_change? : Int = 1,
) -> PdfBytes raise PdfError {
  pdf_lzw_decode_view(data, early_change~)
}