///|
/// A numerically simple, allocation-conscious summary for streaming values.
pub struct OnlineStats {
  count : Int
  mean : Double
  m2 : Double
  minimum : Double
  maximum : Double
} derive(Debug, Eq, ToJson, FromJson)

///|
/// Creates an empty summary. The first observation initializes the extrema.
pub fn OnlineStats::new() -> OnlineStats {
  { count: 0, mean: 0.0, m2: 0.0, minimum: 0.0, maximum: 0.0 }
}

///|
/// Adds one value using Welford's online update.
pub fn OnlineStats::push(self : OnlineStats, value : Double) -> OnlineStats {
  let next_count = self.count + 1
  if self.count == 0 {
    { count: 1, mean: value, m2: 0.0, minimum: value, maximum: value }
  } else {
    let delta = value - self.mean
    let next_mean = self.mean + delta / next_count.to_double()
    let delta2 = value - next_mean
    {
      count: next_count,
      mean: next_mean,
      m2: self.m2 + delta * delta2,
      minimum: self.minimum.min(value),
      maximum: self.maximum.max(value),
    }
  }
}

///|
/// Adds all values in order.
pub fn OnlineStats::push_all(
  self : OnlineStats,
  values : Array[Double],
) -> OnlineStats {
  values.fold(init=self, (summary, value) => summary.push(value))
}

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

///|
pub fn OnlineStats::mean(self : OnlineStats) -> Double {
  if self.count == 0 {
    0.0
  } else {
    self.mean
  }
}

///|
pub fn OnlineStats::sum(self : OnlineStats) -> Double {
  self.mean * self.count.to_double()
}

///|
pub fn OnlineStats::variance(self : OnlineStats) -> Double {
  if self.count < 2 {
    0.0
  } else {
    self.m2 / (self.count - 1).to_double()
  }
}

///|
pub fn OnlineStats::population_variance(self : OnlineStats) -> Double {
  if self.count == 0 {
    0.0
  } else {
    self.m2 / self.count.to_double()
  }
}

///|
pub fn OnlineStats::standard_deviation(self : OnlineStats) -> Double {
  self.variance().sqrt()
}

///|
pub fn OnlineStats::min(self : OnlineStats) -> Double {
  self.minimum
}

///|
pub fn OnlineStats::max(self : OnlineStats) -> Double {
  self.maximum
}

///|
/// Merges two summaries without replaying their observations.
pub fn OnlineStats::merge(
  self : OnlineStats,
  other : OnlineStats,
) -> OnlineStats {
  if other.count == 0 {
    return self
  }
  if self.count == 0 {
    return other
  }
  let total = self.count + other.count
  let delta = other.mean - self.mean
  let weight = other.count.to_double() / total.to_double()
  {
    count: total,
    mean: self.mean + delta * weight,
    m2: self.m2 +
    other.m2 +
    delta *
    delta *
    self.count.to_double() *
    other.count.to_double() /
    total.to_double(),
    minimum: self.minimum.min(other.minimum),
    maximum: self.maximum.max(other.maximum),
  }
}

///|
/// Returns the arithmetic mean, or zero for an empty array.
pub fn mean(values : Array[Double]) -> Double {
  OnlineStats::new().push_all(values).mean()
}

///|
pub fn variance(values : Array[Double]) -> Double {
  OnlineStats::new().push_all(values).variance()
}

///|
pub fn dot(left : Array[Double], right : Array[Double]) -> Double {
  if left.length() != right.length() {
    abort("dot: vector lengths must match")
  }
  let mut result = 0.0
  for i in 0.. Double {
  values.fold(init=0.0, (total, value) => total + value)
}

///|
pub fn weighted_mean(values : Array[Double], weights : Array[Double]) -> Double {
  if values.length() != weights.length() {
    abort("weighted_mean: vector lengths must match")
  }
  let mut numerator = 0.0
  let mut denominator = 0.0
  for i in 0.. Double {
  if left.length() != right.length() || left.length() < 2 {
    abort("covariance: at least two paired observations are required")
  }
  let left_mean = mean(left)
  let right_mean = mean(right)
  let mut total = 0.0
  for i in 0.. Array[Double] {
  if values.length() == 0 {
    abort("normalize_probabilities: values must not be empty")
  }
  for value in values {
    if value < 0.0 {
      abort("probabilities must be non-negative")
    }
  }
  let total = sum(values)
  if total <= 0.0 {
    abort("normalize_probabilities: total must be positive")
  }
  values.map(fn(value) { value / total })
}

///|
/// Deterministic linear interpolation quantile. `q` is in the closed interval [0, 1].
pub fn quantile(values : Array[Double], q : Double) -> Double {
  if values.length() == 0 {
    abort("quantile: values must not be empty")
  }
  if q < 0.0 || q > 1.0 {
    abort("quantile: q must be between zero and one")
  }
  let sorted = values.copy()
  sorted.sort()
  if sorted.length() == 1 {
    return sorted[0]
  }
  let position = q * (sorted.length() - 1).to_double()
  let lower = position.floor().to_int()
  let upper = position.ceil().to_int()
  if lower == upper {
    sorted[lower]
  } else {
    sorted[lower] +
    (sorted[upper] - sorted[lower]) * (position - lower.to_double())
  }
}

///|
/// Returns a copy with the supplied value clamped to an interval.
pub fn clamp(value : Double, lower : Double, upper : Double) -> Double {
  if lower > upper {
    abort("clamp: lower must not exceed upper")
  }
  value.max(lower).min(upper)
}