///| Small dependency-free descriptive statistics used by reproducible decoder

///| experiments. The implementation favours transparent formulas over a

///| heavyweight numerical dependency because benchmark samples are normally

///|
/// short request runs rather than millions of observations.
pub enum StatisticsError {
  EmptySample
  InvalidQuantile(Double)
} derive(Eq, Debug)

///|
/// A one-pass accumulator using Welford's numerically stable variance update.
pub struct OnlineMoments {
  mut count : Int
  mut mean : Double
  mut squared_deviation_sum : Double
  mut minimum : Double
  mut maximum : Double
}

///|
pub fn OnlineMoments::empty() -> OnlineMoments {
  {
    count: 0,
    mean: 0.0,
    squared_deviation_sum: 0.0,
    minimum: 0.0,
    maximum: 0.0,
  }
}

///|
/// Add one observed value without retaining raw experiment records.
pub fn OnlineMoments::add(self : OnlineMoments, value : Double) -> Unit {
  if self.count == 0 {
    self.count = 1
    self.mean = value
    self.minimum = value
    self.maximum = value
    return
  }
  self.count = self.count + 1
  let delta = value - self.mean
  self.mean = self.mean + delta / self.count.to_double()
  let after = value - self.mean
  self.squared_deviation_sum = self.squared_deviation_sum + delta * after
  if value < self.minimum {
    self.minimum = value
  }
  if value > self.maximum {
    self.maximum = value
  }
}

///|
pub fn OnlineMoments::count(self : OnlineMoments) -> Int {
  self.count
}

///|
pub fn OnlineMoments::mean(
  self : OnlineMoments,
) -> Result[Double, StatisticsError] {
  if self.count == 0 {
    Err(EmptySample)
  } else {
    Ok(self.mean)
  }
}

///| Population variance is appropriate for a complete fixed replay suite.

///|
/// Use the unbiased form below when the suite is a sample from production.
pub fn OnlineMoments::population_variance(
  self : OnlineMoments,
) -> Result[Double, StatisticsError] {
  if self.count == 0 {
    Err(EmptySample)
  } else {
    Ok(self.squared_deviation_sum / self.count.to_double())
  }
}

///|
pub fn OnlineMoments::sample_variance(
  self : OnlineMoments,
) -> Result[Double, StatisticsError] {
  if self.count < 2 {
    Err(EmptySample)
  } else {
    Ok(self.squared_deviation_sum / (self.count - 1).to_double())
  }
}

///|
pub fn OnlineMoments::minimum(
  self : OnlineMoments,
) -> Result[Double, StatisticsError] {
  if self.count == 0 {
    Err(EmptySample)
  } else {
    Ok(self.minimum)
  }
}

///|
pub fn OnlineMoments::maximum(
  self : OnlineMoments,
) -> Result[Double, StatisticsError] {
  if self.count == 0 {
    Err(EmptySample)
  } else {
    Ok(self.maximum)
  }
}

///| Copy and insertion-sort a small sample. This deliberately leaves caller

///| ordering untouched, which matters when the same array represents a time

///|
/// trace in a benchmark report.
pub fn sorted_copy(values : Array[Double]) -> Array[Double] {
  let sorted : Array[Double] = []
  for value in values {
    let mut inserted = false
    for position in 0.. Result[Double, StatisticsError] {
  if values.length() == 0 {
    return Err(EmptySample)
  }
  if quantile < 0.0 || quantile > 1.0 {
    return Err(InvalidQuantile(quantile))
  }
  let sorted = sorted_copy(values)
  if sorted.length() == 1 {
    return Ok(sorted[0])
  }
  let location = quantile * (sorted.length() - 1).to_double()
  let lower = location.to_int()
  let upper = if lower + 1 < sorted.length() { lower + 1 } else { lower }
  let fraction = location - lower.to_double()
  Ok(sorted[lower] + (sorted[upper] - sorted[lower]) * fraction)
}

///|
/// A compact immutable summary convenient for serializing experiment results.
pub struct SampleSummary {
  count : Int
  minimum : Double
  maximum : Double
  mean : Double
  population_variance : Double
  median : Double
  p90 : Double
}

///|
pub fn summarize_sample(
  values : Array[Double],
) -> Result[SampleSummary, StatisticsError] {
  if values.length() == 0 {
    return Err(EmptySample)
  }
  let moments = OnlineMoments::empty()
  for value in values {
    moments.add(value)
  }
  let minimum = match moments.minimum() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let maximum = match moments.maximum() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let mean = match moments.mean() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let population_variance = match moments.population_variance() {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let median = match percentile(values, 0.5) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let p90 = match percentile(values, 0.9) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    count: values.length(),
    minimum,
    maximum,
    mean,
    population_variance,
    median,
    p90,
  })
}

///|
pub fn SampleSummary::render(self : SampleSummary, label : String) -> String {
  label +
  " count=" +
  self.count.to_string() +
  " min=" +
  self.minimum.to_string() +
  " mean=" +
  self.mean.to_string() +
  " median=" +
  self.median.to_string() +
  " p90=" +
  self.p90.to_string() +
  " max=" +
  self.maximum.to_string()
}