///|
/// Deterministic replay utilities for incident investigation and regression tests.
pub struct ReplayRecord {
  point : SignalPoint
  result : DetectionResult
  elapsed : Int64
}

///|
pub fn ReplayRecord::new(
  point : SignalPoint,
  result : DetectionResult,
  elapsed : Int64,
) -> ReplayRecord {
  { point, result, elapsed }
}

///|
pub struct ReplaySummary {
  total : Int
  changed : Int
  first_change : Int
  last_change : Int
  mean_score : Double
  max_score : Double
  elapsed : Int64
}

///|
pub fn ReplaySummary::empty() -> ReplaySummary {
  {
    total: 0,
    changed: 0,
    first_change: -1,
    last_change: -1,
    mean_score: 0.0,
    max_score: 0.0,
    elapsed: 0L,
  }
}

///|
pub fn summarize_replay(records : Array[ReplayRecord]) -> ReplaySummary {
  if records.length() == 0 {
    return ReplaySummary::empty()
  }
  let mut changed = 0
  let mut first = -1
  let mut last = -1
  let mut total_score = 0.0
  let mut maximum = 0.0
  let mut elapsed = 0L
  for record in records {
    total_score += record.result.score
    if record.result.score > maximum {
      maximum = record.result.score
    }
    elapsed += record.elapsed
    if record.result.changed {
      if first < 0 {
        first = record.result.index
      }
      last = record.result.index
      changed += 1
    }
  }
  {
    total: records.length(),
    changed,
    first_change: first,
    last_change: last,
    mean_score: total_score / records.length().to_double(),
    max_score: maximum,
    elapsed,
  }
}

///|
pub fn replay_checksum(records : Array[ReplayRecord]) -> Double {
  let mut checksum = 0.0
  for i, record in records {
    checksum += (i + 1).to_double() * (record.point.value + record.result.score)
  }
  checksum
}

///|
pub fn replay_values(records : Array[ReplayRecord]) -> Array[Double] {
  let result : Array[Double] = []
  for record in records {
    result.push(record.point.value)
  }
  result
}

///|
pub fn replay_changed_indices(records : Array[ReplayRecord]) -> Array[Int] {
  let result : Array[Int] = []
  for record in records {
    if record.result.changed {
      result.push(record.result.index)
    }
  }
  result
}

///|
pub fn replay_window(
  records : Array[ReplayRecord],
  start : Int,
  end : Int,
) -> Array[ReplayRecord] {
  let result : Array[ReplayRecord] = []
  let low = if start < 0 { 0 } else { start }
  let high = if end > records.length() { records.length() } else { end }
  if low >= high {
    return result
  }
  for i in low.. Int64 {
  let elapsed : Array[Double] = []
  for record in records {
    elapsed.push(record.elapsed.to_double())
  }
  quantile(elapsed, probability).to_int64()
}

///|
pub fn replay_summary_markdown(summary : ReplaySummary) -> String {
  "| total | changed | first | last | mean score | max score | elapsed |\n|---:|---:|---:|---:|---:|---:|---:|\n| \{summary.total} | \{summary.changed} | \{summary.first_change} | \{summary.last_change} | \{summary.mean_score} | \{summary.max_score} | \{summary.elapsed} |"
}

///|
pub struct ReplayComparator {
  score_tolerance : Double
  confidence_tolerance : Double
  mut compared : Int
  mut mismatches : Int
}

///|
pub fn ReplayComparator::new(
  score_tolerance? : Double = 0.000000001,
  confidence_tolerance? : Double = 0.000000001,
) -> ReplayComparator {
  {
    score_tolerance: if score_tolerance < 0.0 {
      0.0
    } else {
      score_tolerance
    },
    confidence_tolerance: if confidence_tolerance < 0.0 {
      0.0
    } else {
      confidence_tolerance
    },
    compared: 0,
    mismatches: 0,
  }
}

///|
pub fn ReplayComparator::compare(
  self : ReplayComparator,
  expected : DetectionResult,
  actual : DetectionResult,
) -> Bool {
  self.compared += 1
  let same = expected.changed == actual.changed &&
    absolute(expected.score - actual.score) <= self.score_tolerance &&
    absolute(expected.confidence - actual.confidence) <=
    self.confidence_tolerance &&
    expected.index == actual.index
  if !same {
    self.mismatches += 1
  }
  same
}

///|
pub fn ReplayComparator::compared(self : ReplayComparator) -> Int {
  self.compared
}

///|
pub fn ReplayComparator::mismatches(self : ReplayComparator) -> Int {
  self.mismatches
}