///|
/// Online scalar statistics for long-running reconstruction jobs.
pub(all) struct StreamingSummary {
  mut count : Int
  mut mean : Double
  mut variance : Double
  mut minimum : Double
  mut maximum : Double
} derive(Debug, Eq)

///|
pub fn streaming_summary(values : ArrayView[Double]) -> StreamingSummary {
  let summary = {
    count: 0,
    mean: 0.0,
    variance: 0.0,
    minimum: 0.0,
    maximum: 0.0,
  }
  for value in values {
    streaming_add(summary, value)
  }
  summary
}

///|
pub fn streaming_add(summary : StreamingSummary, value : Double) -> Unit {
  let previous = summary.count
  summary.count += 1
  if previous == 0 {
    summary.mean = value
    summary.variance = 0.0
    summary.minimum = value
    summary.maximum = value
  } else {
    let delta = value - summary.mean
    summary.mean += delta / summary.count.to_double()
    let delta2 = value - summary.mean
    summary.variance += delta * delta2
    summary.minimum = summary.minimum.min(value)
    summary.maximum = summary.maximum.max(value)
  }
}

///|
pub fn StreamingSummary::sample_variance(summary : StreamingSummary) -> Double {
  if summary.count < 2 {
    0.0
  } else {
    summary.variance / (summary.count - 1).to_double()
  }
}

///|
pub fn StreamingSummary::population_variance(
  summary : StreamingSummary,
) -> Double {
  if summary.count == 0 {
    0.0
  } else {
    summary.variance / summary.count.to_double()
  }
}

///|
pub fn StreamingSummary::standard_deviation(
  summary : StreamingSummary,
) -> Double {
  summary.population_variance().sqrt()
}

///|
pub fn StreamingSummary::range(summary : StreamingSummary) -> Double {
  if summary.count == 0 {
    0.0
  } else {
    summary.maximum - summary.minimum
  }
}

///|
pub fn StreamingSummary::is_empty(summary : StreamingSummary) -> Bool {
  summary.count == 0
}

///|
pub fn merge_streaming_summary(
  left : StreamingSummary,
  right : StreamingSummary,
) -> StreamingSummary {
  if left.count == 0 {
    return right
  }
  if right.count == 0 {
    return left
  }
  let total = left.count + right.count
  let delta = right.mean - left.mean
  let result = {
    count: total,
    mean: left.mean + delta * right.count.to_double() / total.to_double(),
    variance: left.variance +
    right.variance +
    delta *
    delta *
    left.count.to_double() *
    right.count.to_double() /
    total.to_double(),
    minimum: left.minimum.min(right.minimum),
    maximum: left.maximum.max(right.maximum),
  }
  result
}

///|
pub fn streaming_summary_chunks(
  chunks : ArrayView[Array[Double]],
) -> StreamingSummary {
  let mut result = {
    count: 0,
    mean: 0.0,
    variance: 0.0,
    minimum: 0.0,
    maximum: 0.0,
  }
  for chunk in chunks {
    result = merge_streaming_summary(result, streaming_summary(chunk))
  }
  result
}

///|
pub fn running_mean(values : ArrayView[Double]) -> Array[Double] {
  let result : Array[Double] = []
  let summary = {
    count: 0,
    mean: 0.0,
    variance: 0.0,
    minimum: 0.0,
    maximum: 0.0,
  }
  for value in values {
    streaming_add(summary, value)
    result.push(summary.mean)
  }
  result
}

///|
pub fn running_variance(values : ArrayView[Double]) -> Array[Double] {
  let result : Array[Double] = []
  let summary = {
    count: 0,
    mean: 0.0,
    variance: 0.0,
    minimum: 0.0,
    maximum: 0.0,
  }
  for value in values {
    streaming_add(summary, value)
    result.push(summary.population_variance())
  }
  result
}

///|
pub fn running_minimum(values : ArrayView[Double]) -> Array[Double] {
  let result : Array[Double] = []
  let mut minimum = 0.0
  for i in 0.. Array[Double] {
  let result : Array[Double] = []
  let mut maximum = 0.0
  for i in 0.. Double {
  if values.length() == 0 {
    0.0
  } else {
    let alpha = decay.max(0.0).min(1.0)
    let mut mean = values[0]
    let mut variance = 0.0
    for value in values[1:] {
      let difference = value - mean
      mean += alpha * difference
      variance = (1.0 - alpha) * (variance + alpha * difference * difference)
    }
    variance
  }
}

///|
pub fn covariance_streaming(
  left : ArrayView[Double],
  right : ArrayView[Double],
) -> Double {
  let count = left.length().min(right.length())
  if count == 0 {
    0.0
  } else {
    let left_mean = mean_scalars(left)
    let right_mean = mean_scalars(right)
    let mut sum = 0.0
    for i in 0.. Double {
  let covariance = covariance_streaming(left, right)
  let left_scale = variance_scalars(left).sqrt()
  let right_scale = variance_scalars(right).sqrt()
  if left_scale <= 0.0 || right_scale <= 0.0 {
    0.0
  } else {
    covariance / (left_scale * right_scale)
  }
}