///|
pub struct BenchmarkCase {
  name : String
  sample_size : Int
  mean : Double
  median : Double
  mad : Double
  trimmed_mean : Double
  winsorized_mean : Double
  huber_location : Double
  outlier_count : Int
  covariance : Double
  stream_mean : Double
}

///|
pub fn benchmark_data(size : Int) -> Array[Double] {
  if size < 0 {
    abort("size must not be negative")
  }
  let result = []
  for index = 0; index < size; index = index + 1 {
    let cycle = index % 17
    let baseline = 100.0 + cycle.to_double() * 0.25
    let spike = if index % 97 == 0 {
      250.0
    } else if index % 131 == 0 {
      -180.0
    } else {
      0.0
    }
    result.push(baseline + spike)
  }
  result
}

///|
pub fn benchmark_case(name : String, data : Array[Double]) -> BenchmarkCase {
  let stream = StreamingRobustStats::new(5.0)
  stream.push_many(data)
  let x = []
  let y = []
  for index = 0; index < data.length(); index = index + 1 {
    x.push(index.to_double())
    y.push(data[index] * 0.5)
  }
  {
    name,
    sample_size: data.length(),
    mean: mean(data),
    median: median(data),
    mad: mad(data),
    trimmed_mean: trimmed_mean(data, 0.1),
    winsorized_mean: winsorized_mean(data, 0.1),
    huber_location: huber_location(data),
    outlier_count: outlier_indices_iqr(data).length(),
    covariance: robust_covariance(x, y, 0.1),
    stream_mean: stream.mean(),
  }
}

///|
pub fn benchmark_cases(sizes : Array[Int]) -> Array[BenchmarkCase] {
  let result = []
  for size in sizes {
    result.push(benchmark_case("synthetic-contaminated", benchmark_data(size)))
  }
  result
}

///|
pub fn benchmark_checksum(case : BenchmarkCase) -> Double {
  case.mean +
  case.median +
  case.mad +
  case.trimmed_mean +
  case.winsorized_mean +
  case.huber_location +
  case.covariance +
  case.stream_mean +
  case.outlier_count.to_double()
}

///|
pub fn benchmark_checksums(sizes : Array[Int]) -> Array[Double] {
  let result = []
  for case in benchmark_cases(sizes) {
    result.push(benchmark_checksum(case))
  }
  result
}

///|
pub fn benchmark_summary(case : BenchmarkCase) -> Array[String] {
  [
    "name=" + case.name,
    "sample_size=" + case.sample_size.to_string(),
    "mean=" + case.mean.to_string(),
    "median=" + case.median.to_string(),
    "mad=" + case.mad.to_string(),
    "trimmed_mean=" + case.trimmed_mean.to_string(),
    "winsorized_mean=" + case.winsorized_mean.to_string(),
    "huber_location=" + case.huber_location.to_string(),
    "outlier_count=" + case.outlier_count.to_string(),
    "covariance=" + case.covariance.to_string(),
    "stream_mean=" + case.stream_mean.to_string(),
  ]
}

///|
pub fn benchmark_report(sizes : Array[Int]) -> String {
  let lines = []
  for case in benchmark_cases(sizes) {
    for line in benchmark_summary(case) {
      lines.push(line)
    }
    lines.push("checksum=" + benchmark_checksum(case).to_string())
    lines.push("")
  }
  lines.join("\n")
}

///|
pub fn benchmark_scaling_score(
  small : BenchmarkCase,
  large : BenchmarkCase,
) -> Double {
  if small.sample_size <= 0 || large.sample_size <= 0 {
    0.0
  } else {
    large.sample_size.to_double() / small.sample_size.to_double()
  }
}

///|
pub fn benchmark_reproducible(sizes : Array[Int]) -> Bool {
  benchmark_checksums(sizes) == benchmark_checksums(sizes)
}

///|
pub fn benchmark_contamination_fraction(data : Array[Double]) -> Double {
  outlier_fraction(outlier_indices_iqr(data), data.length())
}

///|
pub fn benchmark_robust_gain(case : BenchmarkCase) -> Double {
  abs_double(case.mean - case.median) / (1.0 + case.mad)
}