///|
fn pdf_fun_join_strings(
  parts : ArrayView[String],
  separator : String,
) -> String {
  let mut text = ""
  for index in 0.. 0 {
      text = text + separator
    }
    text = text + parts[index]
  }
  text
}

///|
/// Return the calculator-language spelling for one operation.
///
/// Procedure operations are formatted recursively in the same brace-delimited
/// form accepted by the type 4 parser.
pub fn PdfCalculatorOp::debug_name(self : PdfCalculatorOp) -> String {
  match self {
    PdfCalculatorIf(ops) => pdf_string_of_calculator(ops) + " if"
    PdfCalculatorIfElse(true_ops, false_ops) =>
      pdf_string_of_calculator(true_ops) +
      " " +
      pdf_string_of_calculator(false_ops) +
      " ifelse"
    PdfCalculatorBool(true) => "true"
    PdfCalculatorBool(false) => "false"
    PdfCalculatorFloat(value) => value.to_string()
    PdfCalculatorInt(value) => value.to_string()
    PdfCalculatorAbs => "abs"
    PdfCalculatorAdd => "add"
    PdfCalculatorAtan => "atan"
    PdfCalculatorCeiling => "ceiling"
    PdfCalculatorCos => "cos"
    PdfCalculatorCvi => "cvi"
    PdfCalculatorCvr => "cvr"
    PdfCalculatorDiv => "div"
    PdfCalculatorExp => "exp"
    PdfCalculatorFloor => "floor"
    PdfCalculatorIdiv => "idiv"
    PdfCalculatorLn => "ln"
    PdfCalculatorLog => "log"
    PdfCalculatorMod => "mod"
    PdfCalculatorMul => "mul"
    PdfCalculatorNeg => "neg"
    PdfCalculatorRound => "round"
    PdfCalculatorSin => "sin"
    PdfCalculatorSqrt => "sqrt"
    PdfCalculatorSub => "sub"
    PdfCalculatorTruncate => "truncate"
    PdfCalculatorAnd => "and"
    PdfCalculatorBitshift => "bitshift"
    PdfCalculatorEq => "eq"
    PdfCalculatorGe => "ge"
    PdfCalculatorGt => "gt"
    PdfCalculatorLe => "le"
    PdfCalculatorLt => "lt"
    PdfCalculatorNe => "ne"
    PdfCalculatorNot => "not"
    PdfCalculatorOr => "or"
    PdfCalculatorXor => "xor"
    PdfCalculatorCopy => "copy"
    PdfCalculatorExch => "exch"
    PdfCalculatorPop => "pop"
    PdfCalculatorDup => "dup"
    PdfCalculatorIndex => "index"
    PdfCalculatorRoll => "roll"
  }
}

///|
/// Format calculator operations as a brace-delimited diagnostic program.
pub fn pdf_string_of_calculator(ops : ArrayView[PdfCalculatorOp]) -> String {
  "{" + pdf_fun_join_strings([ for op in ops => op.debug_name() ], " ") + "}"
}

///|
fn pdf_fun_debug_doubles(values : ArrayView[Double]) -> String {
  pdf_fun_join_strings([ for value in values => value.to_string() ], " ")
}

///|
fn pdf_fun_debug_ints(values : ArrayView[Int]) -> String {
  pdf_fun_join_strings([ for value in values => value.to_string() ], " ")
}

///|
/// Format a parsed function as multi-line diagnostic text.
///
/// The output follows CamlPDF's debug printer shape and is intended for
/// inspection, not for writing a PDF function object.
pub fn PdfFunction::debug_string(self : PdfFunction) -> String {
  let range_text = match self.range {
    Some(values) => pdf_fun_debug_doubles(values)
    None => "null range"
  }
  let prefix = "Domain...\n" +
    pdf_fun_debug_doubles(self.domain) +
    "\n" +
    range_text +
    "\n"
  match self.func {
    PdfFunctionSampled(sampled) =>
      prefix +
      "Sampled\n" +
      "size: " +
      pdf_fun_debug_ints(sampled.size) +
      "\norder: " +
      sampled.order.to_string() +
      "\nencode:\n" +
      pdf_fun_debug_doubles(sampled.encode) +
      "\ndecode:\n" +
      pdf_fun_debug_doubles(sampled.decode) +
      "\noriginal bits per sample..\n" +
      sampled.bits_per_sample.to_string() +
      "\ndata:\n" +
      pdf_fun_debug_ints(sampled.samples)
    PdfFunctionInterpolation(interpolation) =>
      prefix +
      "Interpolation\n" +
      "C0:\n" +
      pdf_fun_debug_doubles(interpolation.c0) +
      "\nC1:\n" +
      pdf_fun_debug_doubles(interpolation.c1) +
      "\nn = " +
      interpolation.n.to_string()
    PdfFunctionStitching(stitching) =>
      prefix +
      "Stitching\n" +
      pdf_fun_join_strings(
        [
          for child in stitching.functions => child.debug_string()
        ],
        "\n",
      ) +
      "\nBounds:\n" +
      pdf_fun_debug_doubles(stitching.bounds) +
      "\nEncode:\n" +
      pdf_fun_debug_doubles(stitching.stitch_encode)
    PdfFunctionCalculator(calculator) =>
      prefix + "Calculator:\n" + pdf_string_of_calculator(calculator.operations)
  }
}

///|
/// Compatibility wrapper for `PdfFunction::debug_string`.
pub fn pdf_string_of_function(function : PdfFunction) -> String {
  function.debug_string()
}