///|
/// MeltingTemp demo: oligonucleotide melting temperatures, ported from
/// Biopython's Bio.SeqUtils.MeltingTemp. Shows the Wallace rule of thumb,
/// empirical GC-content formulas (Tm_GC) and full nearest-neighbor
/// thermodynamics (Tm_NN) with eight published parameter tables, salt /
/// Mg2+ / dNTP corrections, mismatch and dangling-end duplexes,
/// self-complementary strands, custom tables and DMSO/formamide corrections.
///
/// Run: moon run examples/melting_temp_demo

///|
fn p2(x : Double) -> Double {
  (x * 100.0).round() / 100.0
}

///|
fn main {
  println("=== MeltingTemp Demo (oligo melting temperatures) ===\n")

  // A 29-mer primer, the same example used throughout Biopython's
  // MeltingTemp documentation.
  let s = "CGTTCCAAAGATGTGGGCATGAGCTTAC"

  // 1. Wallace rule of thumb: Tm = 4*(G+C) + 2*(A+T), extended with
  //    expected contributions from ambiguous bases.
  println("1. Wallace rule of thumb")
  let wallace = @bio.tm_wallace_full(s) catch { _ => abort("wallace failed") }
  println("   Tm_Wallace(primer) = \{p2(wallace)} C")
  println("   (non-base characters such as digits/spaces are ignored)\n")

  // 2. Empirical GC-content formulas; valueset 7 is the Primer3Plus
  //    formula (default), sets 1-8 cover Marmur-Doty, QuikChange,
  //    Wetmur RNA / RNA-DNA and von Ahsen variants.
  println("2. Tm_GC empirical formulas")
  for vs in [1, 2, 3, 4, 5, 6, 7, 8] {
    let tm = @bio.tm_gc_full(s, valueset=vs) catch {
      _ => abort("tm_gc failed")
    }
    println("   valueset \{vs}: Tm = \{p2(tm)} C")
  }
  // 'X' marks a mismatch position (QuikChange formula)
  let tm_x = @bio.tm_gc_full("CTGCTGATXGCACGAGGTTATGG", valueset=2) catch {
    _ => abort("tm_gc mismatch failed")
  }
  println("   QuikChange set with one 'X' mismatch: Tm = \{p2(tm_x)} C\n")

  // 3. Nearest-neighbor thermodynamics (Allawi & SantaLucia 1997 default)
  println("3. Tm_NN nearest-neighbor thermodynamics")
  let tm_nn = @bio.tm_nn_full(s) catch { _ => abort("tm_nn failed") }
  println("   DNA_NN3 (default), 50 mM Na+: Tm = \{p2(tm_nn)} C")
  for table in ["DNA_NN1", "DNA_NN2", "DNA_NN4"] {
    let tm = @bio.tm_nn_full(s, options=@bio.TmOptions::of(nn_table=table)) catch {
      _ => abort("tm_nn table failed")
    }
    println("   \{table}: Tm = \{p2(tm)} C")
  }
  let r = "CGUUCCAAAGAUGUGGGCAUGAGCUUAC"
  for rtable in ["RNA_NN1", "RNA_NN2", "RNA_NN3"] {
    let tm = @bio.tm_nn_full(r, options=@bio.TmOptions::of(nn_table=rtable)) catch {
      _ => abort("tm_nn rna table failed")
    }
    println("   \{rtable} (RNA): Tm = \{p2(tm)} C")
  }
  let tm_hybrid = @bio.tm_nn_full(
    s,
    options=@bio.TmOptions::of(nn_table="R_DNA_NN1"),
  ) catch {
    _ => abort("tm_nn hybrid failed")
  }
  println("   R_DNA_NN1 (RNA/DNA hybrid): Tm = \{p2(tm_hybrid)} C\n")

  // 4. Salt conditions: Na+ equivalents, Tris, Mg2+ and dNTP binding.
  println("4. Salt, Mg2+ and dNTP effects (salt corrections 1-7)")
  for m in [1, 2, 3, 4, 5, 6, 7] {
    let tm = @bio.tm_nn_full(s, options=@bio.TmOptions::of(saltcorr=m)) catch {
      _ => abort("tm_nn saltcorr failed")
    }
    println("   saltcorr \{m}: Tm = \{p2(tm)} C")
  }
  let tm_tris = @bio.tm_nn_full(s, options=@bio.TmOptions::of(tris=10.0)) catch {
    _ => abort("tm_nn tris failed")
  }
  println("   + 10 mM Tris: Tm = \{p2(tm_tris)} C")
  let tm_mg = @bio.tm_nn_full(
    s,
    options=@bio.TmOptions::of(tris=10.0, mg=1.5, saltcorr=7),
  ) catch {
    _ => abort("tm_nn mg failed")
  }
  println("   + 1.5 mM Mg2+ (Owczarzy 2008): Tm = \{p2(tm_mg)} C")
  let tm_dntp = @bio.tm_nn_full(
    s,
    options=@bio.TmOptions::of(tris=10.0, mg=1.5, dntps=0.6, saltcorr=7),
  ) catch {
    _ => abort("tm_nn dntp failed")
  }
  println("   + 0.6 mM dNTPs (sequester Mg2+): Tm = \{p2(tm_dntp)} C\n")

  // 5. Mismatches and dangling ends: c_seq is the template in 3'->5'
  //    direction; a lowercase base marks the mismatch site.
  println("5. Mismatch and dangling-end corrections")
  let template = "GCAAGGcTTCTACACCCGTACTCGAATG"
  let tm_mis = @bio.tm_nn_full(s, options=@bio.TmOptions::of(c_seq=template)) catch {
    _ => abort("tm_nn mismatch failed")
  }
  println("   one internal mismatch: Tm = \{p2(tm_mis)} C")
  let tm_de = @bio.tm_nn_full(
    s,
    options=@bio.TmOptions::of(shift=1, c_seq="TGCAAGGcTTCTACACCCGTACTCGAATGC"),
  ) catch {
    _ => abort("tm_nn dangling failed")
  }
  println("   mismatch with one dangling end each side: Tm = \{p2(tm_de)} C")
  let tm_self = @bio.tm_nn_full(
    "CGCGAATTCGCG",
    options=@bio.TmOptions::of(selfcomp=true),
  ) catch {
    _ => abort("tm_nn selfcomp failed")
  }
  println("   self-complementary CGCGAATTCGCG: Tm = \{p2(tm_self)} C\n")

  // 6. Chemical additives: DMSO and formamide lower the melting temp.
  println("6. DMSO / formamide chemical corrections")
  let tm_dmso = @bio.tm_chem_correction(70.0, dmso=3.0) catch {
    _ => abort("dmso failed")
  }
  println("   70 C with 3% DMSO: Tm = \{p2(tm_dmso)} C")
  let tm_fmd = @bio.tm_chem_correction(70.0, fmd=5.0) catch {
    _ => abort("fmd failed")
  }
  println("   70 C with 5% formamide: Tm = \{p2(tm_fmd)} C")
}