///|
fn pdf_cursor_read_octal_escape(
  cursor : @core.ByteCursor,
  first : Int,
) -> Byte raise @core.PdfError {
  let mut value = first - 48
  let second = cursor.input_byte()
  if pdf_is_octal_digit_byte(second) {
    value = value * 8 + second - 48
    let third = cursor.input_byte()
    if pdf_is_octal_digit_byte(third) {
      value = value * 8 + third - 48
    } else if third != @core.pdf_no_more {
      cursor.rewind()
    }
  } else if second != @core.pdf_no_more {
    cursor.rewind()
  }
  @core.pdf_byte_of_int(value)
}

///|
fn pdf_cursor_read_string_escape_into(
  cursor : @core.ByteCursor,
  bytes : Array[Byte],
  position : Int,
) -> Int raise @core.PdfError {
  let escaped = cursor.input_byte()
  match escaped {
    110 => pdf_lexeme_write_byte_at(bytes, position, (10).to_byte())
    114 => pdf_lexeme_write_byte_at(bytes, position, (13).to_byte())
    116 => pdf_lexeme_write_byte_at(bytes, position, (9).to_byte())
    98 => pdf_lexeme_write_byte_at(bytes, position, (8).to_byte())
    102 => pdf_lexeme_write_byte_at(bytes, position, (12).to_byte())
    13 => {
      let next = cursor.input_byte()
      if next != 10 && next != @core.pdf_no_more {
        cursor.rewind()
      }
      position
    }
    10 => position
    value if pdf_is_octal_digit_byte(value) =>
      pdf_lexeme_write_byte_at(
        bytes,
        position,
        pdf_cursor_read_octal_escape(cursor, value),
      )
    value if value == @core.pdf_no_more => raise EndOfInput
    value => pdf_lexeme_write_byte_at(bytes, position, value.to_byte())
  }
}

///|
fn pdf_cursor_literal_string_output_capacity(cursor : @core.ByteCursor) -> Int {
  let mut index = cursor.absolute_position()
  let mut capacity = 0
  let mut depth = 1
  while index < cursor.length() && depth > 0 {
    let value = cursor.byte_int_at_absolute(index)
    index += 1
    match value {
      40 => {
        depth += 1
        capacity += 1
      }
      41 => {
        depth -= 1
        if depth > 0 {
          capacity += 1
        }
      }
      92 =>
        if index < cursor.length() {
          let escaped = cursor.byte_int_at_absolute(index)
          index += 1
          match escaped {
            13 =>
              if index < cursor.length() &&
                cursor.byte_int_at_absolute(index) == 10 {
                index += 1
              }
            10 => ()
            value if pdf_is_octal_digit_byte(value) => {
              if index < cursor.length() &&
                pdf_is_octal_digit_byte(cursor.byte_int_at_absolute(index)) {
                index += 1
                if index < cursor.length() &&
                  pdf_is_octal_digit_byte(cursor.byte_int_at_absolute(index)) {
                  index += 1
                }
              }
              capacity += 1
            }
            _ => capacity += 1
          }
        }
      _ => capacity += 1
    }
  }
  capacity
}

///|
/// Lex a PDF literal string.
///
/// Literal strings are enclosed in parentheses, may contain balanced nested
/// parentheses, and support the PDF backslash escapes for control characters,
/// octal bytes, escaped parentheses, and line continuation. Unterminated strings
/// raise `@core.PdfError::EndOfInput`.
pub fn pdf_cursor_lex_literal_string(
  cursor : @core.ByteCursor,
) -> PdfLexeme raise @core.PdfError {
  if cursor.peek_byte() != 40 {
    LexNone
  } else {
    cursor.nudge()
    let bytes = Array::make(
      pdf_cursor_literal_string_output_capacity(cursor),
      b'\x00',
    )
    let mut position = 0
    let mut depth = 1
    while depth > 0 {
      let value = cursor.input_byte()
      match value {
        40 => {
          depth += 1
          position = pdf_lexeme_write_byte_at(bytes, position, (40).to_byte())
        }
        41 => {
          depth -= 1
          if depth > 0 {
            position = pdf_lexeme_write_byte_at(bytes, position, (41).to_byte())
          }
        }
        92 =>
          position = pdf_cursor_read_string_escape_into(cursor, bytes, position)
        value if value == @core.pdf_no_more => raise EndOfInput
        value =>
          position = pdf_lexeme_write_byte_at(bytes, position, value.to_byte())
      }
    }
    LexString(Bytes::from_array(bytes[:position]))
  }
}