///|
pub(all) struct WindowSummary {
  chrom : String
  interval : GenomicInterval?
  record_count : Int
  snv_count : Int
  indel_count : Int
  filtered_count : Int
  mean_qual : Double?
} derive(Debug, Eq)

///|
pub fn summarize_window(
  document : VcfDocument,
  chrom : String,
  interval : GenomicInterval?,
) -> WindowSummary {
  let selected = pipeline_query(
    document,
    chrom,
    match interval {
      Some(value) => value.start
      None => 1
    },
    match interval {
      Some(value) => value.end
      None => 2147483647
    },
  )
  let mut snv_count = 0
  let mut indel_count = 0
  let mut filtered_count = 0
  let mut quality_sum = 0.0
  let mut quality_count = 0
  for record in selected {
    if record_is_snv(record) {
      snv_count = snv_count + 1
    }
    if record_is_indel(record) {
      indel_count = indel_count + 1
    }
    if record.filters.length() > 0 && !record.filters.contains("PASS") {
      filtered_count = filtered_count + 1
    }
    match record.qual {
      Some(value) => {
        quality_sum = quality_sum + value
        quality_count = quality_count + 1
      }
      None => ()
    }
  }
  {
    chrom,
    interval,
    record_count: selected.length(),
    snv_count,
    indel_count,
    filtered_count,
    mean_qual: if quality_count == 0 {
      None
    } else {
      Some(quality_sum / quality_count.to_double())
    },
  }
}

///|
pub fn summarize_fixed_windows(
  document : VcfDocument,
  chrom : String,
  start : Int,
  end : Int,
  width : Int,
) -> Array[WindowSummary] {
  let windows : Array[WindowSummary] = []
  if width <= 0 || start > end {
    return windows
  }
  let mut cursor = start
  while cursor <= end {
    let window_end = min_window(end, cursor + width - 1)
    windows.push(
      summarize_window(document, chrom, make_interval(cursor, window_end)),
    )
    cursor = window_end + 1
  }
  windows
}

///|
pub fn window_density(summary : WindowSummary) -> Double {
  match summary.interval {
    Some(value) if value.end >= value.start =>
      summary.record_count.to_double() / interval_width(Some(value)).to_double()
    _ => 0.0
  }
}

///|
pub fn window_snv_fraction(summary : WindowSummary) -> Double? {
  if summary.record_count == 0 {
    None
  } else {
    Some(summary.snv_count.to_double() / summary.record_count.to_double())
  }
}

///|
pub fn window_filter_fraction(summary : WindowSummary) -> Double? {
  if summary.record_count == 0 {
    None
  } else {
    Some(summary.filtered_count.to_double() / summary.record_count.to_double())
  }
}

///|
pub fn window_is_empty(summary : WindowSummary) -> Bool {
  summary.record_count == 0
}

///|
pub fn window_merge_summaries(
  summaries : Array[WindowSummary],
) -> WindowSummary {
  if summaries.length() == 0 {
    return {
      chrom: "",
      interval: None,
      record_count: 0,
      snv_count: 0,
      indel_count: 0,
      filtered_count: 0,
      mean_qual: None,
    }
  }
  let first = summaries[0]
  let mut record_count = 0
  let mut snv_count = 0
  let mut indel_count = 0
  let mut filtered_count = 0
  let mut quality_sum = 0.0
  let mut quality_count = 0
  for summary in summaries {
    record_count = record_count + summary.record_count
    snv_count = snv_count + summary.snv_count
    indel_count = indel_count + summary.indel_count
    filtered_count = filtered_count + summary.filtered_count
    match summary.mean_qual {
      Some(value) => {
        quality_sum = quality_sum + value
        quality_count = quality_count + 1
      }
      None => ()
    }
  }
  {
    chrom: first.chrom,
    interval: first.interval,
    record_count,
    snv_count,
    indel_count,
    filtered_count,
    mean_qual: if quality_count == 0 {
      None
    } else {
      Some(quality_sum / quality_count.to_double())
    },
  }
}

///|
pub fn format_window_summary(summary : WindowSummary) -> String {
  "\{summary.chrom}\t\{format_interval(summary.interval)}\t\{summary.record_count}\t\{summary.snv_count}\t\{summary.indel_count}\t\{summary.filtered_count}\n"
}

///|
fn min_window(left : Int, right : Int) -> Int {
  if left < right {
    left
  } else {
    right
  }
}