// Performance benchmark for the MoonBit Seq implementation.
//
// Generates a large synthetic DNA sequence and runs each Seq operation
// repeatedly, printing the elapsed time (us) for each. Run with:
//   moon run cmd/bench

///|
/// Generate a deterministic pseudo-random DNA sequence of length `n`
/// using a simple LCG so results are reproducible across runs.
fn gen_dna(n : Int32) -> String {
  let buf = StringBuilder()
  let bases = ['A', 'C', 'G', 'T']
  let mut state = 12345
  for i = 0; i < n; i = i + 1 {
    state = (state * 1103515245 + 12345) & 0x7fffffff
    let idx = state % 4
    buf.write_char(bases[idx])
  }
  buf.to_string()
}

///|
/// Measure how long `f` takes when called `iters` times; returns total us.
fn bench(label : String, iters : Int32, f : () -> Unit) -> Double {
  let ts = @mb.monotonic_clock_start()
  for i = 0; i < iters; i = i + 1 {
    f()
  }
  let elapsed_us = @mb.monotonic_clock_end(ts)
  let per_iter = elapsed_us / iters.to_double()
  println(
    "\{label}: \{elapsed_us} us total, \{per_iter} us/iter (\{iters} iters)",
  )
  elapsed_us
}

///|
/// Measure how long `f` takes when called `iters` times; returns total us.
/// Supports functions that may raise errors.
fn bench_err(label : String, iters : Int32, f : () -> Unit) -> Double {
  let ts = @mb.monotonic_clock_start()
  for i = 0; i < iters; i = i + 1 {
    f()
  }
  let elapsed_us = @mb.monotonic_clock_end(ts)
  let per_iter = elapsed_us / iters.to_double()
  println(
    "\{label}: \{elapsed_us} us total, \{per_iter} us/iter (\{iters} iters)",
  )
  elapsed_us
}

///|

///|
/// Generate a FASTA string with `n_records` records, each of length `seq_len`.
fn gen_fasta(n_records : Int32, seq_len : Int32) -> String {
  let buf = StringBuilder()
  for i = 0; i < n_records; i = i + 1 {
    buf.write_char('>')
    buf.write_string("seq")
    buf.write_string(i.to_string())
    buf.write_string(" Description for sequence ")
    buf.write_string(i.to_string())
    buf.write_char('\n')
    let seq = gen_dna(seq_len)
    buf.write_string(seq)
    buf.write_char('\n')
  }
  buf.to_string()
}

///|
/// Generate a FASTQ string with `n_records` records, each of length `seq_len`.
fn gen_fastq(n_records : Int32, seq_len : Int32) -> String {
  let buf = StringBuilder()
  for i = 0; i < n_records; i = i + 1 {
    buf.write_char('@')
    buf.write_string("seq")
    buf.write_string(i.to_string())
    buf.write_string(" Description ")
    buf.write_string(i.to_string())
    buf.write_char('\n')
    buf.write_string(gen_dna(seq_len))
    buf.write_char('\n')
    buf.write_string("+\n")
    for j = 0; j < seq_len; j = j + 1 {
      buf.write_char('I')
    }
    buf.write_char('\n')
  }
  buf.to_string()
}

///|
fn main {
  let dna = gen_dna(100_000)
  let rna = gen_dna(100_000).replace_all(old="T", new="U")
  let s = @lib.Seq::new(dna)
  let sr = @lib.Seq::new(rna)
  let iters_small = 20
  let iters_find = 200

  println("=== MoonBit Seq benchmark ===")
  println("sequence length: \{dna.char_length()} chars")
  println("iterations (ops): \{iters_small}")
  println("")

  // complement
  ignore(bench("complement", iters_small, fn() { ignore(s.complement()) }))
  ignore(
    bench("complement_rna", iters_small, fn() { ignore(s.complement_rna()) }),
  )
  ignore(
    bench("reverse_complement", iters_small, fn() {
      ignore(s.reverse_complement())
    }),
  )
  ignore(bench("reverse", iters_small, fn() { ignore(s.reverse()) }))

  // transcribe
  ignore(bench("transcribe", iters_small, fn() { ignore(s.transcribe()) }))
  ignore(
    bench("back_transcribe", iters_small, fn() { ignore(sr.back_transcribe()) }),
  )

  // translate
  ignore(
    bench("translate", iters_small, fn() {
      ignore(s.translate()) catch {
        _ => ()
      }
    }),
  )

  // upper / lower
  ignore(bench("upper", iters_small, fn() { ignore(s.upper()) }))
  ignore(bench("lower", iters_small, fn() { ignore(s.lower()) }))

  // count / find
  ignore(bench("count AAA", iters_find, fn() { ignore(s.count("AAA")) }))
  ignore(
    bench("count_overlap AAA", iters_find, fn() {
      ignore(s.count_overlap("AAA"))
    }),
  )
  ignore(bench("find ACGT", iters_find, fn() { ignore(s.find("ACGT")) }))
  ignore(bench("rfind ACGT", iters_find, fn() { ignore(s.rfind("ACGT")) }))

  // startswith / endswith / contains
  ignore(
    bench("startswith ATG", iters_find, fn() { ignore(s.startswith("ATG")) }),
  )
  ignore(bench("endswith TAA", iters_find, fn() { ignore(s.endswith("TAA")) }))
  ignore(bench("contains GGG", iters_find, fn() { ignore(s.contains("GGG")) }))

  // slice
  ignore(
    bench("slice 0..1000", iters_find, fn() {
      ignore(s.slice(start=0, end=1000))
    }),
  )

  // replace
  ignore(
    bench("replace A->N", iters_small, fn() {
      ignore(s.replace(old="A", new="N"))
    }),
  )

  // ---------------------------------------------------------------------------
  // SeqIO benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit SeqIO benchmark ===")
  println("")

  let fasta_content = gen_fasta(100, 1000)
  let fastq_content = gen_fastq(100, 1000)

  // FASTA parse
  ignore(
    bench("parse_fasta (100x1000)", iters_small, fn() {
      ignore(@lib.parse_fasta(fasta_content))
    }),
  )

  // FASTA write
  let fasta_records = @lib.parse_fasta(fasta_content)
  ignore(
    bench("write_fasta (100x1000)", iters_small, fn() {
      ignore(@lib.write_fasta(fasta_records))
    }),
  )

  // FASTQ parse
  ignore(
    bench("parse_fastq (100x1000)", iters_small, fn() {
      ignore(@lib.parse_fastq(fastq_content)) catch {
        _ => ()
      }
    }),
  )

  // FASTQ write
  let fq_records = @lib.parse_fastq(fastq_content) catch {
    _ => Array(capacity=0)
  }
  ignore(
    bench("write_fastq (100x1000)", iters_small, fn() {
      ignore(@lib.write_fastq(fq_records)) catch {
        _ => ()
      }
    }),
  )

  // seqio_parse dispatch
  ignore(
    bench("seqio_parse fasta", iters_small, fn() {
      ignore(@lib.seqio_parse(fasta_content, "fasta")) catch {
        _ => ()
      }
    }),
  )

  ignore(
    bench("seqio_parse fastq", iters_small, fn() {
      ignore(@lib.seqio_parse(fastq_content, "fastq")) catch {
        _ => ()
      }
    }),
  )

  // seqio_to_dict
  ignore(
    bench("seqio_to_dict", iters_small, fn() {
      ignore(@lib.seqio_to_dict(fasta_records))
    }),
  )

  // ---------------------------------------------------------------------------
  // SeqFeature benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit SeqFeature benchmark ===")
  println("")

  let feat_qual = @lib.SeqFeature::new(feature_type="gene")
  let pos0 = @lib.Position::new_exact(0)
  let pos1000 = @lib.Position::new_exact(1000)
  let pos2000 = @lib.Position::new_exact(2000)
  let pos3000 = @lib.Position::new_exact(3000)

  // Position creation
  ignore(
    bench("Position::new_exact", iters_find, fn() {
      ignore(@lib.Position::new_exact(100))
    }),
  )

  // FeatureLocation creation using safe constructor
  ignore(
    bench("FeatureLocation::new_safe", iters_find, fn() {
      ignore(@lib.FeatureLocation::new_safe(pos0, pos1000))
    }),
  )

  // SeqFeature creation
  ignore(
    bench("SeqFeature::new", iters_find, fn() {
      ignore(@lib.SeqFeature::new(feature_type="CDS"))
    }),
  )

  // SeqFeature with location (uses pre-created positions)
  ignore(
    bench("SeqFeature::new with location", iters_find, fn() {
      ignore(
        @lib.SeqFeature::new(
          location=Some(@lib.FeatureLocation::new_safe(pos0, pos1000)),
          feature_type="CDS",
        ),
      )
    }),
  )

  // SeqFeature add_qualifier
  ignore(
    bench("SeqFeature::add_qualifier", iters_find, fn() {
      ignore(feat_qual.add_qualifier("gene", "test"))
    }),
  )

  // CompoundLocation
  let loc1 = @lib.FeatureLocation::new_safe(pos0, pos1000)
  let loc2 = @lib.FeatureLocation::new_safe(pos2000, pos3000)
  ignore(
    bench("CompoundLocation::new", iters_find, fn() {
      ignore(@lib.CompoundLocation::new([loc1, loc2], operator="join"))
    }),
  )

  // ---------------------------------------------------------------------------
  // SeqUtils benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit SeqUtils benchmark ===")
  println("")

  let dna_seq = gen_dna(10000)

  // CheckSum
  ignore(bench("crc32", iters_find, fn() { ignore(@lib.crc32(dna_seq)) }))
  ignore(bench("gcg", iters_find, fn() { ignore(@lib.gcg(dna_seq)) }))
  ignore(bench("seguid", iters_small, fn() { ignore(@lib.seguid(dna_seq)) }))

  // GC content
  ignore(bench("gc", iters_find, fn() { ignore(@lib.gc(dna_seq)) }))

  // ProteinAnalysis
  let protein_seq = "MAEGEITTFTALTEKFNLPPGNYKKPKLLYCSNGGHFLRILPDGTVDGTRDRSDQHIQLQLSAESVGEVYIKSTETGQYLAMDTSGLLYGSQTPSEECLFLERLEENHYNTYTSKKHAEKNWFVGLKKNGSCKRGPRTHYGQKAILFLPLPV"
  let pa = @lib.ProteinAnalysis::new(protein_seq)
  ignore(
    bench("ProteinAnalysis::count_amino_acids", iters_find, fn() {
      ignore(pa.count_amino_acids())
    }),
  )
  ignore(
    bench("ProteinAnalysis::molecular_weight", iters_find, fn() {
      ignore(pa.molecular_weight())
    }),
  )
  ignore(
    bench("ProteinAnalysis::aromaticity", iters_find, fn() {
      ignore(pa.aromaticity())
    }),
  )
  ignore(
    bench("ProteinAnalysis::gravy", iters_find, fn() { ignore(pa.gravy()) }),
  )

  // MeltingTemp
  ignore(
    bench("tm_wallace", iters_find, fn() { ignore(@lib.tm_wallace(dna_seq)) }),
  )
  ignore(bench("tm_gc", iters_find, fn() { ignore(@lib.tm_gc(dna_seq)) }))

  // ---------------------------------------------------------------------------
  // Bio.Phylo benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit Bio.Phylo benchmark ===")
  println("")

  let simple_newick = "(A:0.1,B:0.2,(C:0.3,D:0.4):0.5);"
  let tree = @lib.parse_newick(simple_newick)

  // Newick parsing
  ignore(
    bench("parse_newick (simple)", iters_find, fn() {
      ignore(@lib.parse_newick(simple_newick))
    }),
  )

  // Newick writing
  ignore(
    bench("write_newick", iters_find, fn() {
      ignore(@lib.write_newick(tree, true))
    }),
  )

  // Count terminals
  ignore(
    bench("Tree::get_leaf_count", iters_find, fn() {
      ignore(@lib.get_leaf_count(tree))
    }),
  )

  // Get terminals
  ignore(
    bench("Tree::get_leaves", iters_find, fn() { ignore(@lib.get_leaves(tree)) }),
  )

  // Get nonterminals
  ignore(
    bench("Tree::get_node_count - get_leaf_count", iters_find, fn() {
      ignore(@lib.get_node_count(tree) - @lib.get_leaf_count(tree))
    }),
  )

  // Find node by name
  ignore(
    bench("Tree::find_node by name", iters_find, fn() {
      ignore(@lib.find_node(tree, "A"))
    }),
  )

  // Get tree depth
  ignore(
    bench("Tree::get_tree_depth", iters_find, fn() {
      ignore(@lib.get_tree_depth(tree))
    }),
  )

  // Total branch length
  ignore(
    bench("Tree::total_branch_length", iters_find, fn() {
      ignore(@lib.total_branch_length(tree))
    }),
  )

  // Get node count
  ignore(
    bench("Tree::get_node_count", iters_find, fn() {
      ignore(@lib.get_node_count(tree))
    }),
  )

  // Draw ASCII
  ignore(
    bench("Tree::write_newick", iters_small, fn() {
      ignore(@lib.write_newick(tree, true))
    }),
  )

  // Generate a larger tree for more meaningful benchmarks
  let large_newick_buf = StringBuilder()
  large_newick_buf.write_char('(')
  for i = 0; i < 50; i = i + 1 {
    if i > 0 {
      large_newick_buf.write_char(',')
    }
    large_newick_buf.write_string("X")
    large_newick_buf.write_string(i.to_string())
    large_newick_buf.write_char(':')
    large_newick_buf.write_string(0.1.to_string())
  }
  large_newick_buf.write_string(");")
  let large_newick = large_newick_buf.to_string()
  let large_tree = @lib.parse_newick(large_newick)

  // Large tree operations
  ignore(
    bench("parse_newick (50 leaves)", iters_small, fn() {
      ignore(@lib.parse_newick(large_newick))
    }),
  )

  ignore(
    bench("Tree::get_leaf_count (50 leaves)", iters_small, fn() {
      ignore(@lib.get_leaf_count(large_tree))
    }),
  )

  ignore(
    bench("Tree::get_leaves (50 leaves)", iters_small, fn() {
      ignore(@lib.get_leaves(large_tree))
    }),
  )

  // ---------------------------------------------------------------------------
  // Bio.PDB benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit Bio.PDB benchmark ===")
  println("")

  // Generate PDB content
  let pdb_buf = StringBuilder()
  pdb_buf.write_string("MODEL     1\n")
  for i = 0; i < 100; i = i + 1 {
    pdb_buf.write_string("ATOM      1  N   ALA A   ")
    pdb_buf.write_string((i + 1).to_string())
    pdb_buf.write_string(
      "      0.000   0.000   0.000  1.00  0.00           N\n",
    )
    pdb_buf.write_string("ATOM      2  CA  ALA A   ")
    pdb_buf.write_string((i + 1).to_string())
    pdb_buf.write_string(
      "      1.500   0.000   0.000  1.00  0.00           C\n",
    )
    pdb_buf.write_string("ATOM      3  C   ALA A   ")
    pdb_buf.write_string((i + 1).to_string())
    pdb_buf.write_string(
      "      3.000   0.000   0.000  1.00  0.00           C\n",
    )
  }
  pdb_buf.write_string("ENDMDL\n")
  pdb_buf.write_string("END\n")
  let pdb_content = pdb_buf.to_string()

  // PDB parsing
  ignore(
    bench("parse_pdb (300 atoms)", iters_small, fn() {
      ignore(@lib.parse_pdb(pdb_content))
    }),
  )

  // PDB writing
  let pdb_struct = @lib.parse_pdb(pdb_content)
  ignore(
    bench("write_pdb (300 atoms)", iters_small, fn() {
      ignore(@lib.write_pdb(pdb_struct))
    }),
  )

  // Structure traversal
  ignore(
    bench("Structure::get_atoms", iters_find, fn() {
      ignore(pdb_struct.get_atoms())
    }),
  )

  ignore(
    bench("Structure::get_residues", iters_find, fn() {
      ignore(pdb_struct.get_residues())
    }),
  )

  ignore(
    bench("Structure::get_chains", iters_find, fn() {
      ignore(pdb_struct.get_chains())
    }),
  )

  // Distance calculation
  let atoms = pdb_struct.get_atoms()
  if atoms.length() >= 2 {
    let atom1 = atoms[0]
    let atom2 = atoms[1]
    ignore(
      bench("Atom::distance", iters_find, fn() { ignore(atom1.distance(atom2)) }),
    )
  }

  // Center of mass
  let residues = pdb_struct.get_residues()
  if residues.length() > 0 {
    let residue = residues[0]
    ignore(
      bench("Residue::center_of_mass", iters_find, fn() {
        ignore(residue.center_of_mass())
      }),
    )
  }

  // Find atom by name
  if residues.length() > 0 {
    let residue = residues[0]
    ignore(
      bench("Residue::find_atom", iters_find, fn() {
        ignore(residue.find_atom("CA"))
      }),
    )
  }

  // Structure distance
  ignore(
    bench("Structure::distance", iters_find, fn() {
      ignore(pdb_struct.distance("CA", "N"))
    }),
  )

  // ---------------------------------------------------------------------------
  // BAM benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit BAM benchmark ===")
  println("")

  let test_bam_base64 = "QkFNAS0AAABASEQJVk46MS42CVNPOmNvb3JkaW5hdGUKQFNRCVNOOmNocjEJTE46MTAwMAoBAAAABQAAAGNocjEA6AMAADkAAAAAAAAAZAAAAAAAAAA8AAAGAAEACAAAAP////8AAAAAAAAAAHJlYWQxAIAAAAAAAAAAAAAAAAAAAAA="

  // BAM parse from base64
  ignore(
    bench_err("parse_bam_base64 (1 record)", iters_find, () => {
      ignore(@lib.parse_bam_base64(test_bam_base64)) catch {
        _ => ()
      }
    }),
  )

  // BAM to_sam
  let bam_file_opt = @lib.try_parse_bam_base64(test_bam_base64)
  if bam_file_opt is Some(_) {
    let bam_file = bam_file_opt.unwrap()
    ignore(
      bench("BamFile::to_sam", iters_find, fn() { ignore(bam_file.to_sam()) }),
    )

    // BAM get_reference_names
    ignore(
      bench("BamFile::get_reference_names", iters_find, fn() {
        ignore(bam_file.get_reference_names())
      }),
    )

    // BAM num_references
    ignore(
      bench("BamFile::num_references", iters_find, fn() {
        ignore(bam_file.num_references())
      }),
    )

    // BAM len
    ignore(bench("BamFile::len", iters_find, fn() { ignore(bam_file.len()) }))
  }

  // ---------------------------------------------------------------------------
  // CRAM benchmarks
  // ---------------------------------------------------------------------------
  println("")
  println("=== MoonBit CRAM benchmark ===")
  println("")

  // Generate CRAM test data
  let cram_buf = StringBuilder()
  cram_buf.write_string("CRAM")
  cram_buf.write_char('\u{0001}')
  cram_buf.write_char('\u{0003}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0001}')
  cram_buf.write_char('\u{0004}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_char('\u{0000}')
  cram_buf.write_string("@HD\tVN:1.6\tSO:coordinate\n@SQ\tSN:chr1\tLN:1000\n")
  let cram_content = cram_buf.to_string()

  // CRAM magic check
  ignore(
    bench("is_cram_magic", iters_find, fn() {
      ignore(@lib.is_cram_magic(cram_content))
    }),
  )

  // CRAM magic check (negative)
  ignore(
    bench("is_cram_magic (negative)", iters_find, fn() {
      ignore(@lib.is_cram_magic("BAMtest"))
    }),
  )

  println("")
  println("done.")
}