///|
fn pdf_ua_matterhorn_is_form_stream(
  document : PdfDocument,
  object : @syntax.PdfObject,
) -> Bool {
  match document.lookup_direct(pdf_ua_subtype_key(), object) {
    Some(PdfNameObject(subtype)) => subtype == pdf_ua_matterhorn_form()
    _ => false
  }
}

///|
fn PdfDocument::pdf_ua_matterhorn_stream_resources(
  self : PdfDocument,
  stream : @syntax.PdfObject,
) -> @syntax.PdfObject {
  match self.lookup_direct(pdf_ua_matterhorn_resources_key(), stream) {
    Some(resources) => resources
    None => PdfDictionary([])
  }
}

///|
fn PdfDocument::pdf_ua_matterhorn_all_ops(
  self : PdfDocument,
  xobjects? : Bool = true,
) -> Array[Array[@content.PdfContentOp]] raise @core.PdfError {
  let output : Array[Array[@content.PdfContentOp]] = []
  if xobjects {
    for entry in self.stream_object_entries() {
      let stream = @syntax.PdfStreamObject(entry.2)
      if pdf_ua_matterhorn_is_form_stream(self, stream) {
        let resources = self.pdf_ua_matterhorn_stream_resources(stream)
        output.push(self.parse_content_ops_with_resources(resources, [stream]))
      }
    }
  }
  for page in self.pages_of_pagetree() {
    output.push(
      self.parse_content_ops_with_resources(page.resources, page.content),
    )
  }
  output
}

///|
fn pdf_ua_matterhorn_artifact_in_content_ops(
  ops : ArrayView[@content.PdfContentOp],
) -> Bool {
  let mut in_content = false
  let mut in_artifact = false
  for op in ops {
    match op {
      Op_BDC(_, _) => in_content = true
      Op_BMC(name) if name == pdf_ua_matterhorn_artifact() =>
        if in_content {
          return true
        } else {
          in_artifact = true
        }
      Op_EMC =>
        if in_content && in_artifact {
          return true
        } else if in_content {
          in_content = false
        } else {
          in_artifact = false
        }
      _ => ()
    }
  }
  false
}

///|
fn pdf_ua_matterhorn_content_in_artifact_ops(
  ops : ArrayView[@content.PdfContentOp],
) -> Bool {
  let mut in_content = false
  let mut in_artifact = false
  for op in ops {
    match op {
      Op_BDC(_, _) => if in_artifact { return true } else { in_content = true }
      Op_BMC(name) if name == pdf_ua_matterhorn_artifact() => in_artifact = true
      Op_EMC =>
        if in_content && in_artifact {
          return true
        } else if in_content {
          in_content = false
        } else {
          in_artifact = false
        }
      _ => ()
    }
  }
  false
}

///|
fn pdf_ua_matterhorn_real_op(op : @content.PdfContentOp) -> Bool {
  match op {
    Op_m(_, _)
    | Op_l(_, _)
    | Op_c(_, _, _, _, _, _)
    | Op_v(_, _, _, _)
    | Op_y(_, _, _, _)
    | Op_h
    | Op_re(_, _, _, _)
    | Op_S
    | Op_s
    | Op_f
    | Op_F
    | Op_fstar
    | Op_B
    | Op_Bstar
    | Op_b
    | Op_bstar
    | Op_W
    | Op_Wstar
    | Op_Tj(_)
    | Op_TJ(_)
    | Op_quote(_)
    | Op_double_quote(_, _, _)
    | Op_sh(_)
    | Op_InlineImage(_, _, _)
    | Op_Do(_) => true
    _ => false
  }
}

///|
fn pdf_ua_matterhorn_path_construction_op(op : @content.PdfContentOp) -> Bool {
  match op {
    Op_m(_, _)
    | Op_l(_, _)
    | Op_c(_, _, _, _, _, _)
    | Op_v(_, _, _, _)
    | Op_y(_, _, _, _)
    | Op_h
    | Op_re(_, _, _, _) => true
    _ => false
  }
}

///|
fn pdf_ua_matterhorn_bdc_context(
  tag : @core.PdfName,
  properties : @syntax.PdfObject,
) -> PdfUAMatterhornContext {
  match properties {
    PdfDictionary(_) if properties.lookup_immediate(
        pdf_ua_matterhorn_mcid_key(),
      )
      is Some(_) => PdfUAMatterhornContent
    _ =>
      if tag == pdf_ua_matterhorn_artifact() {
        PdfUAMatterhornArtifact
      } else {
        PdfUAMatterhornOther
      }
  }
}

///|
fn pdf_ua_matterhorn_bmc_context(tag : @core.PdfName) -> PdfUAMatterhornContext {
  if tag == pdf_ua_matterhorn_span() || tag == pdf_ua_matterhorn_artifact() {
    PdfUAMatterhornArtifact
  } else {
    PdfUAMatterhornOther
  }
}

///|
fn pdf_ua_matterhorn_stack_has_marking(
  stack : ArrayView[PdfUAMatterhornContext],
) -> Bool {
  for context in stack {
    match context {
      PdfUAMatterhornArtifact | PdfUAMatterhornContent => return true
      PdfUAMatterhornOther => ()
    }
  }
  false
}

///|
fn pdf_ua_matterhorn_naked_ops(
  ops : ArrayView[@content.PdfContentOp],
) -> (Array[@content.PdfContentOp], Json?) {
  let naked : Array[@content.PdfContentOp] = []
  let stack : Array[PdfUAMatterhornContext] = []
  for op in ops {
    match op {
      Op_BDC(tag, properties) =>
        stack.push(pdf_ua_matterhorn_bdc_context(tag, properties))
      Op_BMC(tag) => stack.push(pdf_ua_matterhorn_bmc_context(tag))
      Op_EMC =>
        if stack.length() == 0 {
          return ([], Some(Json::string("empty stack")))
        } else {
          ignore(stack.pop())
        }
      _ =>
        if !pdf_ua_matterhorn_stack_has_marking(stack) &&
          pdf_ua_matterhorn_real_op(op) {
          naked.push(op)
        }
    }
  }
  (naked, None)
}

///|
fn pdf_ua_matterhorn_unfiltered_real_op(
  naked : ArrayView[@content.PdfContentOp],
) -> Bool {
  let reversed = naked.rev()
  let mut index = 0
  while index < reversed.length() {
    match reversed[index] {
      Op_W | Op_Wstar => {
        index += 1
        while index < reversed.length() &&
              pdf_ua_matterhorn_path_construction_op(reversed[index]) {
          index += 1
        }
      }
      op => {
        if pdf_ua_matterhorn_real_op(op) {
          return true
        }
        index += 1
      }
    }
  }
  false
}

///|
fn PdfDocument::pdf_ua_matterhorn_check_01_003(
  self : PdfDocument,
) -> Json? raise @core.PdfError {
  for ops in self.pdf_ua_matterhorn_all_ops() {
    if pdf_ua_matterhorn_artifact_in_content_ops(ops) {
      return Some(Json::null())
    }
  }
  None
}

///|
fn PdfDocument::pdf_ua_matterhorn_check_01_004(
  self : PdfDocument,
) -> Json? raise @core.PdfError {
  for ops in self.pdf_ua_matterhorn_all_ops() {
    if pdf_ua_matterhorn_content_in_artifact_ops(ops) {
      return Some(Json::null())
    }
  }
  None
}

///|
fn PdfDocument::pdf_ua_matterhorn_check_01_005(
  self : PdfDocument,
) -> Json? raise @core.PdfError {
  for ops in self.pdf_ua_matterhorn_all_ops(xobjects=false) {
    let (naked, error) = pdf_ua_matterhorn_naked_ops(ops)
    match error {
      Some(extra) => return Some(extra)
      None =>
        if pdf_ua_matterhorn_unfiltered_real_op(naked) {
          return Some(Json::null())
        }
    }
  }
  None
}

///|
fn PdfDocument::pdf_ua_matterhorn_check_01_007(self : PdfDocument) -> Json? {
  match
    self.lookup_chain(self.trailer_dict(), [
      pdf_ua_root_key(),
      pdf_ua_markinfo_key(),
      pdf_ua_matterhorn_suspects_key(),
    ]) {
    Some(PdfBoolean(true)) => Some(Json::null())
    _ => None
  }
}