///|
/// Thresholds for a numerical benchmark gate.
pub(all) struct BenchmarkGate {
  max_l2 : Double
  max_linf : Double
  max_mass_drift : Double
} derive(Debug)

///|
/// Construct a benchmark gate.
pub fn BenchmarkGate::new(
  max_l2~ : Double,
  max_linf~ : Double,
  max_mass_drift~ : Double,
) -> BenchmarkGate {
  { max_l2, max_linf, max_mass_drift }
}

///|
/// Check a benchmark result against configured thresholds.
pub fn BenchmarkGate::accepts(
  self : BenchmarkGate,
  report : BenchmarkReport,
) -> Bool {
  report.l2_error <= self.max_l2 &&
  report.linf_error <= self.max_linf &&
  report.mass_drift <= self.max_mass_drift &&
  report.stable
}

///|
/// Comparison between two measured benchmark results.
pub(all) struct BenchmarkComparison {
  l2_delta : Double
  relative_l2 : Double
  linf_delta : Double
  mass_delta : Double
} derive(Debug)

///|
/// Compare the error and conservation fields of two reports.
pub fn compare_reports(
  left : BenchmarkReport,
  right : BenchmarkReport,
) -> BenchmarkComparison {
  let l2_delta = left.l2_error - right.l2_error
  let reference = abs_double(right.l2_error)
  {
    l2_delta,
    relative_l2: if reference == 0.0 {
      abs_double(l2_delta)
    } else {
      abs_double(l2_delta) / reference
    },
    linf_delta: left.linf_error - right.linf_error,
    mass_delta: left.mass_drift - right.mass_drift,
  }
}

///|
/// Return the better of two stable reports by L2 error.
pub fn better_report(
  left : BenchmarkReport,
  right : BenchmarkReport,
) -> BenchmarkReport {
  if left.stable && (!right.stable || left.l2_error <= right.l2_error) {
    left
  } else {
    right
  }
}

///|
/// Run a small resolution study for Poiseuille flow.
pub fn run_resolution_study(
  resolutions~ : Array[Int],
  steps~ : Int,
) -> Array[BenchmarkReport] {
  let result = Array::new()
  for resolution in resolutions {
    let height = (resolution / 3).max(4)
    result.push(
      run_poiseuille_benchmark(width=resolution.max(8), height~, steps~),
    )
  }
  result
}

///|
/// Return the report with the smallest L2 error.
pub fn best_benchmark(reports : ArrayView[BenchmarkReport]) -> BenchmarkReport? {
  let mut best : BenchmarkReport? = None
  for report in reports {
    match best {
      None => best = Some(report)
      Some(current) => best = Some(better_report(current, report))
    }
  }
  best
}

///|
/// Return the largest mass drift in a report set.
pub fn maximum_mass_drift(reports : ArrayView[BenchmarkReport]) -> Double {
  let mut result = 0.0
  for report in reports {
    result = result.max(report.mass_drift)
  }
  result
}

///|
/// Return true when every report is stable and finite.
pub fn benchmark_suite_is_usable(reports : ArrayView[BenchmarkReport]) -> Bool {
  let mut result = true
  for report in reports {
    result = result &&
      report.stable &&
      !report.l2_error.is_nan() &&
      !report.linf_error.is_nan()
  }
  result
}