///|
pub(all) struct SampleStats {
  sample_count : Int
  called_count : Int
  missing_count : Int
  invalid_count : Int
  alt_allele_count : Int
  total_dosage : Int?
  heterozygous_count : Int
  homozygous_reference_count : Int
  homozygous_alternate_count : Int
} derive(Debug, Eq)

///|
pub fn summarize_sample_calls(
  calls : Array[SampleCall],
  key : String,
) -> SampleStats {
  let mut called_count = 0
  let mut missing_count = 0
  let mut invalid_count = 0
  let mut alt_allele_count = 0
  let mut total_dosage = 0
  let mut has_dosage = true
  let mut heterozygous_count = 0
  let mut homozygous_reference_count = 0
  let mut homozygous_alternate_count = 0
  for sample in calls {
    match sample_value(sample, key) {
      Some(VcfValue::String(raw)) => {
        let genotype = decode_genotype(raw)
        match genotype_class(genotype) {
          GenotypeClass::Invalid => invalid_count = invalid_count + 1
          GenotypeClass::Missing => missing_count = missing_count + 1
          GenotypeClass::Heterozygous => {
            called_count = called_count + 1
            heterozygous_count = heterozygous_count + 1
          }
          GenotypeClass::Reference => {
            called_count = called_count + 1
            homozygous_reference_count = homozygous_reference_count + 1
          }
          GenotypeClass::HomozygousAlternate => {
            called_count = called_count + 1
            homozygous_alternate_count = homozygous_alternate_count + 1
          }
          MultiAlternate => called_count = called_count + 1
        }
        alt_allele_count = alt_allele_count + genotype_alt_count(genotype)
        if genotype.invalid {
          has_dosage = false
        }
        match genotype_dosage(genotype) {
          Some(value) => total_dosage = total_dosage + value
          None => ()
        }
      }
      Some(VcfValue::Missing) | None => missing_count = missing_count + 1
      _ => invalid_count = invalid_count + 1
    }
  }
  {
    sample_count: calls.length(),
    called_count,
    missing_count,
    invalid_count,
    alt_allele_count,
    total_dosage: if has_dosage {
      Some(total_dosage)
    } else {
      None
    },
    heterozygous_count,
    homozygous_reference_count,
    homozygous_alternate_count,
  }
}

///|
pub fn summarize_document_samples(
  document : VcfDocument,
  key : String,
) -> Array[(String, SampleStats)] {
  let grouped : Array[(String, Array[SampleCall])] = []
  for record in document.records {
    for sample in record.samples {
      let mut found = false
      for index, pair in grouped {
        if pair.0 == sample.name {
          grouped[index].1.push(sample)
          found = true
          break
        }
      }
      if !found {
        grouped.push((sample.name, [sample]))
      }
    }
  }
  let result : Array[(String, SampleStats)] = []
  for pair in grouped {
    result.push((pair.0, summarize_sample_calls(pair.1, key)))
  }
  result
}

///|
pub fn sample_call_rate(stats : SampleStats) -> Double {
  if stats.sample_count == 0 {
    0.0
  } else {
    stats.called_count.to_double() / stats.sample_count.to_double()
  }
}

///|
pub fn sample_alt_frequency(stats : SampleStats) -> Double? {
  if stats.sample_count == 0 {
    None
  } else {
    Some(
      stats.alt_allele_count.to_double() / (stats.sample_count * 2).to_double(),
    )
  }
}

///|
pub fn sample_missing_rate(stats : SampleStats) -> Double {
  if stats.sample_count == 0 {
    0.0
  } else {
    stats.missing_count.to_double() / stats.sample_count.to_double()
  }
}

///|
pub fn sample_is_high_quality(
  stats : SampleStats,
  minimum_call_rate : Double,
) -> Bool {
  sample_call_rate(stats) >= minimum_call_rate && stats.invalid_count == 0
}

///|
pub fn merge_sample_stats(
  left : SampleStats,
  right : SampleStats,
) -> SampleStats {
  {
    sample_count: left.sample_count + right.sample_count,
    called_count: left.called_count + right.called_count,
    missing_count: left.missing_count + right.missing_count,
    invalid_count: left.invalid_count + right.invalid_count,
    alt_allele_count: left.alt_allele_count + right.alt_allele_count,
    total_dosage: match (left.total_dosage, right.total_dosage) {
      (Some(a), Some(b)) => Some(a + b)
      _ => None
    },
    heterozygous_count: left.heterozygous_count + right.heterozygous_count,
    homozygous_reference_count: left.homozygous_reference_count +
    right.homozygous_reference_count,
    homozygous_alternate_count: left.homozygous_alternate_count +
    right.homozygous_alternate_count,
  }
}