///|
pub(all) struct QualitySummary {
  count : Int
  missing : Int
  minimum : Double?
  maximum : Double?
  mean : Double?
} derive(Debug, Eq)

///|
pub fn analyze_quality(records : Array[VcfRecord]) -> QualitySummary {
  let mut count = 0
  let mut missing = 0
  let mut total = 0.0
  let mut minimum : Double? = None
  let mut maximum : Double? = None
  for record in records {
    match record.qual {
      Some(value) => {
        count = count + 1
        total = total + value
        match minimum {
          Some(old) if old <= value => ()
          _ => minimum = Some(value)
        }
        match maximum {
          Some(old) if old >= value => ()
          _ => maximum = Some(value)
        }
      }
      None => missing = missing + 1
    }
  }
  {
    count,
    missing,
    minimum,
    maximum,
    mean: if count == 0 {
      None
    } else {
      Some(total / count.to_double())
    },
  }
}

///|
pub fn quality_passes(summary : QualitySummary, threshold : Double) -> Bool {
  match summary.minimum {
    Some(value) => value >= threshold
    None => false
  }
}

///|
pub fn quality_range(summary : QualitySummary) -> Double? {
  match (summary.minimum, summary.maximum) {
    (Some(low), Some(high)) => Some(high - low)
    _ => None
  }
}

///|
pub fn quality_missing_rate(summary : QualitySummary) -> Double {
  let total = summary.count + summary.missing
  if total == 0 {
    0.0
  } else {
    summary.missing.to_double() / total.to_double()
  }
}

///|
pub fn collect_reference_bases(
  records : Array[VcfRecord],
) -> Array[(String, Int)] {
  let counts : Array[(String, Int)] = []
  for record in records {
    increment_string_count(counts, record.ref_allele)
  }
  counts
}

///|
pub fn collect_alternate_bases(
  records : Array[VcfRecord],
) -> Array[(String, Int)] {
  let counts : Array[(String, Int)] = []
  for record in records {
    for alt in record.alt_alleles {
      increment_string_count(counts, alt)
    }
  }
  counts
}

///|
fn increment_string_count(counts : Array[(String, Int)], key : String) -> Unit {
  for index, pair in counts {
    if pair.0 == key {
      counts[index] = (key, pair.1 + 1)
      return
    }
  }
  counts.push((key, 1))
}

///|
pub fn analyze_transitions(records : Array[VcfRecord]) -> Int {
  let mut count = 0
  for record in records {
    if record.ref_allele.length() == 1 {
      for alt in record.alt_alleles {
        if is_transition_base(record.ref_allele, alt) {
          count = count + 1
        }
      }
    }
  }
  count
}

///|
fn is_transition_base(ref_allele : String, alt : String) -> Bool {
  if alt.length() != 1 {
    false
  } else {
    match (ref_allele.get_char(0), alt.get_char(0)) {
      (Some(r), Some(a)) =>
        (r == 'A' && a == 'G') ||
        (r == 'G' && a == 'A') ||
        (r == 'C' && a == 'T') ||
        (r == 'T' && a == 'C')
      _ => false
    }
  }
}

///|
pub fn analyze_transversions(records : Array[VcfRecord]) -> Int {
  let mut count = 0
  for record in records {
    if record.ref_allele.length() == 1 {
      for alt in record.alt_alleles {
        if alt.length() == 1 &&
          !is_transition_base(record.ref_allele, alt) &&
          alt != record.ref_allele {
          count = count + 1
        }
      }
    }
  }
  count
}

///|
pub fn transition_transversion_ratio(records : Array[VcfRecord]) -> Double? {
  let transitions = analyze_transitions(records)
  let transversions = analyze_transversions(records)
  if transversions == 0 {
    None
  } else {
    Some(transitions.to_double() / transversions.to_double())
  }
}

///|
pub fn analyze_filter_rate(records : Array[VcfRecord]) -> Double {
  if records.length() == 0 {
    0.0
  } else {
    let filtered = records
      .filter(fn(record) {
        record.filters.length() > 0 && !record.filters.contains("PASS")
      })
      .length()
    filtered.to_double() / records.length().to_double()
  }
}

///|
pub fn analyze_info_presence(records : Array[VcfRecord], key : String) -> Int {
  records.filter(fn(record) { record_has_info(record, key) }).length()
}

///|
pub fn analyze_id_count(records : Array[VcfRecord]) -> Int {
  records
  .map(fn(record) { record.ids.length() })
  .fold(init=0, fn(total, count) { total + count })
}

///|
pub fn analyze_alt_count(records : Array[VcfRecord]) -> Int {
  records.map(record_alt_count).fold(init=0, fn(total, count) { total + count })
}

///|
pub fn analyze_class_counts(
  records : Array[VcfRecord],
) -> Array[(VariantClass, Int)] {
  pipeline_record_counts_by_class({
    header: VcfHeader::{ fileformat: "", meta: [], columns: [], samples: [] },
    records,
  })
}

///|
pub fn analyze_record_length(record : VcfRecord) -> Int {
  record.ref_allele.length() +
  record.alt_alleles
  .map(fn(alt) { alt.length() })
  .fold(init=0, fn(total, length) { total + length })
}

///|
pub fn analyze_total_bases(records : Array[VcfRecord]) -> Int {
  records
  .map(analyze_record_length)
  .fold(init=0, fn(total, length) { total + length })
}

///|
pub fn analyze_contig_span(
  records : Array[VcfRecord],
  chrom : String,
) -> GenomicInterval? {
  let mut first : Int? = None
  let mut last : Int? = None
  for record in records {
    if record.chrom == chrom {
      match first {
        None => first = Some(record.pos)
        Some(_) => ()
      }
      match last {
        Some(old) if old >= record.pos => ()
        _ => last = Some(record.pos)
      }
    }
  }
  match (first, last) {
    (Some(start), Some(end)) => make_interval(start, end)
    _ => None
  }
}

///|
pub fn analyze_sample_count(records : Array[VcfRecord]) -> Int {
  let names : Array[String] = []
  for record in records {
    for sample in record.samples {
      if !names.contains(sample.name) {
        names.push(sample.name)
      }
    }
  }
  names.length()
}

///|
pub fn analyze_duplicate_keys(records : Array[VcfRecord]) -> Int {
  let seen : Array[String] = []
  let mut duplicates = 0
  for record in records {
    let key = record_key(record)
    if seen.contains(key) {
      duplicates = duplicates + 1
    } else {
      seen.push(key)
    }
  }
  duplicates
}