///|
/// bio - BioSeqs 命令行工具(seqkit 风格).
///
/// 用法: bio <子命令> [FASTA/FASTQ 文本 | --demo]
/// 注: MoonBit core 无文件系统接口, 序列文本经参数传入;
/// 嵌入宿主程序读取文件后调用库 API 可获得完整文件处理能力.
/// 示例: moon run cmd/bio -- stats "$(cat seqs.fasta)"
///       moon run cmd/bio -- fq-qc --demo
fn bio_demo_fasta() -> String {
  ">seq1 kinase-like\nMKTLLLTLVVMKTAYIAKQRQISFVKSHFSRQLEER\n>seq2 promoter-region\nGCGCGTTTAAACGCGCGTTTAAACGGCCGGTT\n>seq3 at-rich\nAATTAATTAATTGCAAATTAA\n"
}

///|
fn bio_demo_fastq() -> String {
  "@r1 first read\nACGTACGTACGT\n+\nIIIIIIIIIIII\n@r2\nGGGGCCCCGGGG\n+\nFFFFFFFFFFFF\n@r3\nTTTTAAAATTTTAAAA\n+\n!!!!!!!!!!!!!!!!\n"
}

///|
fn bio_print_help() -> Unit {
  println("bio - BioSeqs 命令行工具 (seqkit 风格)")
  println("")
  println("用法: bio <子命令> [FASTA/FASTQ 文本 | --demo]")
  println("")
  println("子命令:")
  println("  gc      序列 GC 含量 (FASTA)")
  println("  stats   FASTA 统计: 条数/总长/均值/N50/最长")
  println(
    "  fq-qc   FASTQ 基础质控: reads 数/读长分布/质量均值/GC",
  )
  println("  rc      反向互补 (FASTA 输入输出)")
  println("  trans   翻译为蛋白 (FASTA -> FASTA, 框 1, NCBI 表 1)")
  println("  info    每条记录摘要: id/长度/GC%/分子量")
  println("")
  println("选项:")
  println("  --demo  使用内置示例数据")
  println("  -h      显示本帮助")
}

///|
fn bio_input_text(args : Array[String]) -> String? {
  let parts : Array[String] = []
  for a in args {
    if a == "--demo" {
      return Some(bio_demo_fasta())
    }
    if a.length() > 0 {
      parts.push(a)
    }
  }
  if parts.length() == 0 {
    return None
  }
  Some(parts.join("\n"))
}

///|
fn bio_require_input(cmd : String, args : Array[String]) -> String? {
  match bio_input_text(args) {
    Some(text) => Some(text)
    None => {
      println("错误: 子命令 '" + cmd + "' 需要序列文本或 --demo")
      None
    }
  }
}

///|
fn bio_is_fastq(cmd : String, args : Array[String]) -> Bool {
  for a in args {
    if a == "--demo" {
      return cmd == "fq-qc"
    }
  }
  false
}

///|
fn bio_pct(v : Double) -> String {
  (v * 100.0).to_string() + "%"
}

///|
fn bio_n50(lengths : Array[Int32]) -> Int32 {
  if lengths.length() == 0 {
    return 0
  }
  let sorted = lengths.copy()
  let mut i = 0
  while i < sorted.length() {
    let mut j = i + 1
    while j < sorted.length() {
      if sorted[j] > sorted[i] {
        let t = sorted[i]
        sorted[i] = sorted[j]
        sorted[j] = t
      }
      j = j + 1
    }
    i = i + 1
  }
  let total = {
    let mut s = 0
    for l in sorted {
      s = s + l
    }
    s
  }
  let mut acc = 0
  for l in sorted {
    acc = acc + l
    if acc * 2 >= total {
      return l
    }
  }
  0
}

///|
fn bio_cmd_gc(args : Array[String]) -> Unit {
  let input = match bio_require_input("gc", args) {
    Some(t) => t
    None => return
  }
  let records = @lib.parse_fasta(input)
  if records.length() == 0 {
    println("(无记录)")
    return
  }
  let mut total_len = 0
  let mut total_gc = 0.0
  for rec in records {
    let s = rec.seq.to_string()
    let gc = @lib.gc_content(s)
    total_len = total_len + s.length()
    total_gc = total_gc + gc * s.length().to_double()
    println("  " + rec.id + "\t" + bio_pct(gc))
  }
  let overall = if total_len > 0 {
    total_gc / total_len.to_double()
  } else {
    0.0
  }
  println("  合计\t" + bio_pct(overall))
}

///|
fn bio_cmd_stats(args : Array[String]) -> Unit {
  let input = match bio_require_input("stats", args) {
    Some(t) => t
    None => return
  }
  let lengths : Array[Int32] = []
  @lib.fasta_each_record(input, fn(rec) { lengths.push(rec.seq.length()) })
  if lengths.length() == 0 {
    println("(无记录)")
    return
  }
  let mut total = 0
  let mut max_len = 0
  let mut min_len = lengths[0]
  for l in lengths {
    total = total + l
    if l > max_len {
      max_len = l
    }
    if l < min_len {
      min_len = l
    }
  }
  let mean = total.to_double() / lengths.length().to_double()
  println("  条数:    " + lengths.length().to_string())
  println("  总长:    " + total.to_string())
  println("  均长:    " + mean.to_string())
  println("  最短:    " + min_len.to_string())
  println("  最长:    " + max_len.to_string())
  println("  N50:     " + bio_n50(lengths).to_string())
}

///|
fn bio_cmd_fq_qc(args : Array[String]) -> Unit raise @lib.SeqIOError {
  let input = if bio_is_fastq("fq-qc", args) {
    bio_demo_fastq()
  } else {
    match bio_require_input("fq-qc", args) {
      Some(t) => t
      None => return
    }
  }
  let mut n_reads = 0
  let mut total_bases = 0
  let mut q_sum = 0
  let mut q_count = 0
  let mut gc_bases = 0
  let mut min_len = -1
  let mut max_len = -1
  @lib.fastq_each_record(input, fn(rec) {
    let s = rec.seq.to_string()
    n_reads = n_reads + 1
    total_bases = total_bases + s.length()
    for i in 0..
        for q in quals {
          q_sum = q_sum + q
          q_count = q_count + 1
        }
      None => ()
    }
    if min_len < 0 || s.length() < min_len {
      min_len = s.length()
    }
    if max_len < 0 || s.length() > max_len {
      max_len = s.length()
    }
  })
  if n_reads == 0 {
    println("(无 reads)")
    return
  }
  let mean_len = total_bases.to_double() / n_reads.to_double()
  let mean_q = if q_count > 0 {
    q_sum.to_double() / q_count.to_double()
  } else {
    0.0
  }
  let gc = if total_bases > 0 {
    gc_bases.to_double() / total_bases.to_double()
  } else {
    0.0
  }
  println("  reads:   " + n_reads.to_string())
  println("  碱基:    " + total_bases.to_string())
  println(
    "  读长:    min=" +
    min_len.to_string() +
    " mean=" +
    mean_len.to_string() +
    " max=" +
    max_len.to_string(),
  )
  println("  平均Q:   " + mean_q.to_string())
  println("  GC:      " + bio_pct(gc))
}

///|
fn bio_cmd_rc(args : Array[String]) -> Unit {
  let input = match bio_require_input("rc", args) {
    Some(t) => t
    None => return
  }
  let records = @lib.parse_fasta(input)
  for rec in records {
    let rc = @lib.Seq::new(rec.seq.to_string()).reverse_complement().to_string()
    println(">" + rec.description + "_rc")
    println(rc)
  }
}

///|
fn bio_cmd_trans(args : Array[String]) -> Unit {
  let input = match bio_require_input("trans", args) {
    Some(t) => t
    None => return
  }
  let records = @lib.parse_fasta(input)
  for rec in records {
    let protein : String? = Some(
      @lib.Seq::new(rec.seq.to_string()).translate().to_string(),
    ) catch {
      SeqError(msg) => {
        println(">" + rec.description + "_protein [翻译失败: " + msg + "]")
        None
      }
    }
    match protein {
      Some(p) => {
        println(">" + rec.description + "_protein")
        println(p)
      }
      None => ()
    }
  }
}

///|
fn bio_cmd_info(args : Array[String]) -> Unit {
  let input = match bio_require_input("info", args) {
    Some(t) => t
    None => return
  }
  let records = @lib.parse_fasta(input)
  println("  id\t长度\tGC%\t分子量(Da)")
  for rec in records {
    let s = rec.seq.to_string()
    let gc = @lib.gc_content(s)
    let mw = @lib.sequtils_molecular_weight(s, "DNA")
    println(
      "  " +
      rec.id +
      "\t" +
      s.length().to_string() +
      "\t" +
      bio_pct(gc) +
      "\t" +
      mw.to_string(),
    )
  }
}

///|
fn main {
  let argv = @env.args()
  let rest : Array[String] = []
  for i, a in argv {
    // argv[0] 为程序路径/程序名(moon run 下为 .wasm 路径), 跳过
    let is_prog = i == 0 && (a == "bio" || a.find("/") is Some(_))
    if a.length() > 0 && !is_prog {
      rest.push(a)
    }
  }
  if rest.length() == 0 || rest[0] == "-h" || rest[0] == "--help" {
    bio_print_help()
    return
  }
  let cmd = rest[0]
  let args : Array[String] = []
  for i in 1.. bio_cmd_gc(args)
      "stats" => bio_cmd_stats(args)
      "fq-qc" => bio_cmd_fq_qc(args)
      "rc" => bio_cmd_rc(args)
      "trans" => bio_cmd_trans(args)
      "info" => bio_cmd_info(args)
      _ => {
        println("未知子命令: " + cmd)
        bio_print_help()
      }
    }
  } catch {
    SeqIOError(msg) => println("解析错误: " + msg)
  }
}