///|
fn pdf_coord_update_last(
  numbers : Array[Double],
  operation : String,
  value : Double,
) -> Bool {
  if numbers.length() == 0 {
    false
  } else {
    let index = numbers.length() - 1
    match operation {
      "add" => numbers[index] = numbers[index] + value
      "sub" => numbers[index] = numbers[index] - value
      "mul" => numbers[index] = numbers[index] * value
      "div" => numbers[index] = numbers[index] / value
      _ => return false
    }
    true
  }
}

///|
fn pdf_coord_parse_units_tokens(
  document : PdfDocument,
  page : @page.PdfPage,
  source : String,
  tokens : ArrayView[PdfCoordToken],
) -> Array[Double] raise @core.PdfError {
  let numbers : Array[Double] = Array(capacity=tokens.length())
  let mut index = 0
  let mut done = false
  while index < tokens.length() && !done {
    match tokens[index] {
      CoordWord(word) =>
        match pdf_coord_paper_size(word) {
          Some(paper) => {
            let (width, height) = pdf_coord_paper_points(paper)
            numbers.push(width)
            numbers.push(height)
            index += 1
          }
          None =>
            if word == "pt" {
              index += 1
            } else if word == "add" ||
              word == "sub" ||
              word == "mul" ||
              word == "div" {
              if index + 1 >= tokens.length() {
                raise BadNumberArgument(source)
              }
              let unit = if index + 2 < tokens.length() {
                match tokens[index + 2] {
                  CoordWord(unit) if pdf_coord_is_unit(unit) => unit
                  _ => "pt"
                }
              } else {
                "pt"
              }
              match
                pdf_coord_token_number(document, page, tokens[index + 1], unit) {
                Some(value) => {
                  if !pdf_coord_update_last(numbers, word, value) {
                    raise BadNumberArgument(source)
                  }
                  index += if unit == "pt" { 2 } else { 3 }
                }
                None => raise BadNumberArgument(source)
              }
            } else {
              match pdf_coord_page_characteristic(document, page, word) {
                Some(value) => {
                  numbers.push(value)
                  index += 1
                }
                None => done = true
              }
            }
        }
      CoordNumber(value) =>
        if index + 1 < tokens.length() {
          match tokens[index + 1] {
            CoordWord(unit) =>
              match pdf_coord_apply_unit(value, unit) {
                Some(converted) => {
                  numbers.push(converted)
                  index += 2
                }
                None => {
                  numbers.push(value)
                  index += 1
                }
              }
            _ => {
              numbers.push(value)
              index += 1
            }
          }
        } else {
          numbers.push(value)
          index += 1
        }
    }
  }
  numbers
}

///|
/// Parse a coordinate expression with page-relative units and variables for the
/// supplied page.
pub fn PdfDocument::parse_units_string(
  self : PdfDocument,
  page : @page.PdfPage,
  text : String,
) -> Array[Double] raise @core.PdfError {
  pdf_coord_parse_units_tokens(self, page, text, pdf_coord_tokenize(text))
}

///|
/// Function wrapper for `PdfDocument::parse_units_string`.
pub fn pdf_parse_units_string(
  document : PdfDocument,
  page : @page.PdfPage,
  text : String,
) -> Array[Double] raise @core.PdfError {
  document.parse_units_string(page, text)
}