///|
/// Aggregate quality statistics across benchmark results.
pub(all) struct BenchmarkStatistics {
  count : Int
  stable_count : Int
  mean_l2 : Double
  mean_linf : Double
  min_speed : Double
  max_speed : Double
  quality_score : Double
} derive(Debug)

///|
/// Compute aggregate benchmark statistics.
pub fn benchmark_statistics(
  reports : ArrayView[BenchmarkReport],
) -> BenchmarkStatistics {
  if reports.length() == 0 {
    {
      count: 0,
      stable_count: 0,
      mean_l2: 0.0,
      mean_linf: 0.0,
      min_speed: 0.0,
      max_speed: 0.0,
      quality_score: 0.0,
    }
  } else {
    let mut l2 = 0.0
    let mut linf = 0.0
    let mut minimum = reports[0].mean_speed
    let mut maximum = reports[0].mean_speed
    let mut stable = 0
    for report in reports {
      l2 += report.l2_error
      linf += report.linf_error
      minimum = minimum.min(report.mean_speed)
      maximum = maximum.max(report.mean_speed)
      if report.stable {
        stable += 1
      }
    }
    let count = reports.length()
    {
      count,
      stable_count: stable,
      mean_l2: l2 / count.to_double(),
      mean_linf: linf / count.to_double(),
      min_speed: minimum,
      max_speed: maximum,
      quality_score: stable.to_double() / count.to_double() * 100.0,
    }
  }
}

///|
/// Export aggregate benchmark statistics.
pub fn BenchmarkStatistics::to_csv(self : BenchmarkStatistics) -> String {
  "\{self.count},\{self.stable_count},\{self.mean_l2},\{self.mean_linf},\{self.min_speed},\{self.max_speed},\{self.quality_score}\n"
}

///|
/// Return whether an aggregate meets a minimum quality score.
pub fn BenchmarkStatistics::passes(
  self : BenchmarkStatistics,
  minimum_score? : Double = 100.0,
) -> Bool {
  self.count > 0 && self.quality_score >= minimum_score
}

///|
/// Return the most stable report by maximum speed.
pub fn safest_report(reports : ArrayView[BenchmarkReport]) -> BenchmarkReport? {
  let mut safest : BenchmarkReport? = None
  for report in reports {
    match safest {
      None => safest = Some(report)
      Some(current) =>
        if report.stable && report.mean_speed < current.mean_speed {
          safest = Some(report)
        }
    }
  }
  safest
}

///|
/// Return an error trend between two reports.
pub fn error_trend(
  earlier : BenchmarkReport,
  later : BenchmarkReport,
) -> Double {
  later.l2_error - earlier.l2_error
}

///|
/// Return true when a later report has lower L2 error.
pub fn error_improved(
  earlier : BenchmarkReport,
  later : BenchmarkReport,
) -> Bool {
  later.l2_error < earlier.l2_error
}