///|
fn maf_integer_array_text(values : Array[Int32]) -> String {
  let output = StringBuilder()
  output.write_char('[')
  for index = 0; index < values.length(); index = index + 1 {
    if index > 0 {
      output.write_string(", ")
    }
    output.write_string(values[index].to_string())
  }
  output.write_char(']')
  output.to_string()
}

///|
fn main {
  println("=== Biopython Bio.Align.maf Demo ===")

  let document = @src.align_maf_parse(@src.align_maf_example_text()) catch {
    AlignMafError(message) => abort("MAF parsing failed: " + message)
  }
  let track = match document.track {
    Some(value) => value
    None => abort("example MAF track metadata is missing")
  }

  println("\n1. Parse MAF header, track metadata, and a/s/i/e/q records")
  println(
    "  version=" +
    document.version +
    ", scoring=" +
    document.scoring.unwrap_or("none") +
    ", program=" +
    document.program.unwrap_or("none"),
  )
  println(
    "  track=" +
    track.name.unwrap_or("unnamed") +
    ", visibility=" +
    track.visibility.unwrap_or("default") +
    ", species=" +
    track.species_order.length().to_string(),
  )
  println(
    "  blocks=" +
    document.blocks.length().to_string() +
    ", comments=" +
    document.comments.length().to_string(),
  )
  let first = document.blocks[0]
  let reference = first.components[0]
  let insertion = match reference.insertion {
    Some(value) =>
      value.left_status +
      ":" +
      value.left_count.to_string() +
      "/" +
      value.right_status +
      ":" +
      value.right_count.to_string()
    None => "none"
  }
  println(
    "  reference=" +
    reference.source +
    ", sequence=" +
    reference.sequence() +
    ", insertion=" +
    insertion +
    ", quality=" +
    reference.quality.unwrap_or("none"),
  )
  println(
    "  empty component=" +
    first.empty_components[0].source +
    ", status=" +
    first.empty_components[0].status,
  )

  println("\n2. Build absolute coordinate paths across plus/minus strands")
  let path = @src.align_maf_coordinate_path(first)
  for point in path {
    println(
      "  column " +
      point.column.to_string() +
      " -> " +
      maf_integer_array_text(point.positions),
    )
  }
  let to_mouse = @src.align_maf_map_position(
    first, "hg38.chr7", 100, "mm39.chr5",
  ) catch {
    AlignMafError(message) => abort("MAF position mapping failed: " + message)
  }
  let to_human = @src.align_maf_map_position(
    first, "mm39.chr5", 449, "hg38.chr7",
  ) catch {
    AlignMafError(message) => abort("MAF reverse mapping failed: " + message)
  }
  println(
    "  hg38:100 -> mm39:" +
    to_mouse.unwrap_or(-1).to_string() +
    "; mm39:449 -> hg38:" +
    to_human.unwrap_or(-1).to_string(),
  )

  println("\n3. Summarize the multi-block document")
  let summary = document.summary()
  println(
    "  components=" +
    summary.component_count.to_string() +
    ", empty=" +
    summary.empty_component_count.to_string() +
    ", aligned columns=" +
    summary.aligned_columns.to_string(),
  )
  println(
    "  reference bases=" +
    summary.reference_bases.to_string() +
    ", distinct sources=" +
    summary.source_count.to_string(),
  )

  println("\n4. Query a MafIndex-style half-open reference interval")
  let index = @src.AlignMafIndex::create(document, "hg38.chr7") catch {
    AlignMafError(message) => abort("MAF index creation failed: " + message)
  }
  let hits = index.search(103, 111) catch {
    AlignMafError(message) => abort("MAF interval search failed: " + message)
  }
  println(
    "  entries=" +
    index.entry_count().to_string() +
    ", overlaps [103, 111)=" +
    hits.length().to_string(),
  )

  println("\n5. Splice multiple exons with missing-data filling")
  let spliced = index.get_spliced([102, 110], [106, 113]) catch {
    AlignMafError(message) => abort("MAF splicing failed: " + message)
  }
  println("  hg38.chr7: " + spliced.sequence("hg38.chr7").unwrap_or("missing"))
  println("  mm39.chr5: " + spliced.sequence("mm39.chr5").unwrap_or("missing"))
  println("  output columns: " + spliced.columns.to_string())

  println("\n6. Write canonical MAF and parse it again")
  let canonical = @src.align_maf_write(document) catch {
    AlignMafError(message) => abort("MAF serialization failed: " + message)
  }
  let round_trip = @src.align_maf_parse(canonical) catch {
    AlignMafError(message) => abort("MAF round-trip parsing failed: " + message)
  }
  println("  output bytes: " + canonical.length().to_string())
  println("  round-trip preserved: " + (round_trip == document).to_string())
}