///|
/// Additional descriptive statistics for quality engineers.
pub fn sum_of(values : Array[Double]) -> Double {
  let mut result = 0.0
  for value in values {
    result += value
  }
  result
}

///|
pub fn product_of(values : Array[Double]) -> Double {
  let mut result = 1.0
  for value in values {
    result *= value
  }
  result
}

///|
pub fn weighted_mean(
  values : Array[Double],
  weights : Array[Double],
) -> Double? {
  if values.length() == 0 || values.length() != weights.length() {
    None
  } else {
    let mut numerator = 0.0
    let mut denominator = 0.0
    for index in 0.. Double? {
  if values.length() == 0 {
    None
  } else {
    let mut total = 1.0
    for value in values {
      if value <= 0.0 {
        return None
      }
      total *= value
    }
    Some(nth_root(total, values.length()))
  }
}

///|
/// Binary-search an integer-degree root using only multiplication.
fn nth_root(value : Double, degree : Int) -> Double {
  let mut low = 0.0
  let mut high = value.max(1.0)
  for _ in 0..<48 {
    let middle = (low + high) / 2.0
    let mut power = 1.0
    for _ in 0.. Double? {
  if values.length() == 0 {
    None
  } else {
    let mut reciprocal = 0.0
    for value in values {
      if value <= 0.0 {
        return None
      }
      reciprocal += 1.0 / value
    }
    Some(values.length().to_double() / reciprocal)
  }
}

///|
pub fn covariance(first : Array[Double], second : Array[Double]) -> Double? {
  if first.length() < 2 || first.length() != second.length() {
    None
  } else {
    let first_mean = mean_of(first)
    let second_mean = mean_of(second)
    let mut total = 0.0
    for index in 0.. Double? {
  match
    (
      covariance(first, second),
      covariance(first, first),
      covariance(second, second),
    ) {
    (Some(cross), Some(first_var), Some(second_var)) =>
      if first_var <= 0.0 || second_var <= 0.0 {
        None
      } else {
        Some(cross / (first_var * second_var).sqrt())
      }
    _ => None
  }
}

///|
pub fn autocorrelation(values : Array[Double], lag : Int) -> Double? {
  if lag < 0 || lag >= values.length() || values.length() - lag < 2 {
    None
  } else {
    let first : Array[Double] = []
    let second : Array[Double] = []
    for index in lag.. Array[Frequency] {
  let result : Array[Frequency] = []
  for value in values {
    let mut found = false
    for entry in result {
      if entry.value == value {
        found = true
      }
    }
    if !found {
      result.push({ value, count: 0, proportion: 0.0 })
    }
  }
  for index in 0.. Array[Double]? {
  rolling_extreme(values, width, true)
}

///|
/// Maximum of each rolling window.
pub fn rolling_max(values : Array[Double], width : Int) -> Array[Double]? {
  rolling_extreme(values, width, false)
}

///|
fn rolling_extreme(
  values : Array[Double],
  width : Int,
  minimum : Bool,
) -> Array[Double]? {
  if width <= 0 || width > values.length() {
    None
  } else {
    let result : Array[Double] = []
    for start in 0..<(values.length() - width + 1) {
      let mut extreme = values[start]
      for index in (start + 1)..<(start + width) {
        if minimum {
          extreme = extreme.min(values[index])
        } else {
          extreme = extreme.max(values[index])
        }
      }
      result.push(extreme)
    }
    Some(result)
  }
}

///|
/// Range of each rolling window.
pub fn rolling_range(values : Array[Double], width : Int) -> Array[Double]? {
  match (rolling_min(values, width), rolling_max(values, width)) {
    (Some(low), Some(high)) => {
      let result : Array[Double] = []
      for index in 0.. None
  }
}

///|
/// Remove a symmetric fraction of the smallest and largest observations.
pub fn trimmed(values : Array[Double], fraction : Double) -> Array[Double]? {
  if fraction < 0.0 || fraction >= 0.5 || values.length() == 0 {
    None
  } else {
    let ordered = sorted_copy(values)
    let remove = (fraction * values.length().to_double()).to_int()
    let result : Array[Double] = []
    for index in remove..<(ordered.length() - remove) {
      result.push(ordered[index])
    }
    Some(result)
  }
}

///|
/// Replace tail observations with the nearest retained quantile.
pub fn winsorized(values : Array[Double], fraction : Double) -> Array[Double]? {
  if fraction < 0.0 || fraction >= 0.5 || values.length() == 0 {
    None
  } else {
    let ordered = sorted_copy(values)
    let amount = (fraction * values.length().to_double()).to_int()
    let low = ordered[amount]
    let high = ordered[ordered.length() - amount - 1]
    let result : Array[Double] = []
    for value in values {
      result.push(value.max(low).min(high))
    }
    Some(result)
  }
}

///|
/// Clamp every measurement to a trusted engineering interval.
pub fn clipped(
  values : Array[Double],
  lower : Double,
  upper : Double,
) -> Array[Double]? {
  if lower > upper {
    None
  } else {
    let result : Array[Double] = []
    for value in values {
      result.push(value.max(lower).min(upper))
    }
    Some(result)
  }
}

///|
/// Histogram bin with an inclusive lower and exclusive upper edge.
pub(all) struct HistogramBin {
  lower : Double
  upper : Double
  count : Int
  proportion : Double
} derive(Debug, ToJson)

///|
pub fn histogram(values : Array[Double], bins : Int) -> Array[HistogramBin]? {
  if values.length() == 0 || bins <= 0 {
    None
  } else {
    let mut low = values[0]
    let mut high = values[0]
    for value in values {
      low = low.min(value)
      high = high.max(value)
    }
    if low == high {
      None
    } else {
      let width = (high - low) / bins.to_double()
      let counts : Array[Int] = []
      for _ in 0..= bins {
          index = bins - 1
        }
        counts[index] += 1
      }
      let result : Array[HistogramBin] = []
      for index in 0.. Double? {
  if values.length() == 0 {
    None
  } else {
    let mut below = 0
    let mut equal = 0
    for candidate in values {
      if candidate < value {
        below += 1
      }
      if candidate == value {
        equal += 1
      }
    }
    Some(
      (below.to_double() + 0.5 * equal.to_double()) /
      values.length().to_double(),
    )
  }
}

///|
/// Standard error of the sample mean.
pub fn standard_error(values : Array[Double]) -> Double? {
  if values.length() < 2 {
    None
  } else {
    Some(std_of(values, mean_of(values)) / values.length().to_double().sqrt())
  }
}

///|
/// Approximate 95 percent confidence interval for a mean.
pub fn confidence_interval_95(values : Array[Double]) -> (Double, Double)? {
  match standard_error(values) {
    None => None
    Some(error) => {
      let center = mean_of(values)
      Some((center - 1.96 * error, center + 1.96 * error))
    }
  }
}

///|
/// Pooled sample deviation for independent subgroups.
pub fn pooled_standard_deviation(groups : Array[Array[Double]]) -> Double? {
  let mut degrees = 0
  let mut total = 0.0
  for group in groups {
    if group.length() < 2 {
      return None
    }
    let mean = mean_of(group)
    for value in group {
      let delta = value - mean
      total += delta * delta
    }
    degrees += group.length() - 1
  }
  if degrees <= 0 {
    None
  } else {
    Some((total / degrees.to_double()).sqrt())
  }
}

///|
/// Boolean outlier markers using the interquartile range fence.
pub fn iqr_outliers(
  values : Array[Double],
  multiplier : Double,
) -> Array[Bool]? {
  if multiplier < 0.0 {
    None
  } else {
    match (quantile(values, 0.25), quantile(values, 0.75)) {
      (Some(first), Some(third)) => {
        let spread = third - first
        let low = first - multiplier * spread
        let high = third + multiplier * spread
        let result : Array[Bool] = []
        for value in values {
          result.push(value < low || value > high)
        }
        Some(result)
      }
      _ => None
    }
  }
}

///|
/// Count sign changes around a center line.
pub fn sign_changes(values : Array[Double], center : Double) -> Int {
  let mut changes = 0
  let mut previous : Int? = None
  for value in values {
    let sign = if value > center { 1 } else if value < center { -1 } else { 0 }
    if sign != 0 {
      match previous {
        Some(old) => if old != sign { changes += 1 }
        None => ()
      }
      previous = Some(sign)
    }
  }
  changes
}

///|
/// Longest consecutive run on either side of a center line.
pub fn longest_run(values : Array[Double], center : Double) -> Int {
  let mut longest = 0
  let mut current = 0
  let mut side = 0
  for value in values {
    let next = if value > center { 1 } else if value < center { -1 } else { 0 }
    if next == 0 {
      current = 0
      side = 0
    } else if next == side {
      current += 1
    } else {
      current = 1
      side = next
    }
    longest = longest.max(current)
  }
  longest
}

///|
/// Count monotonic direction changes, useful for tool-wear review.
pub fn direction_changes(values : Array[Double]) -> Int {
  if values.length() < 3 {
    0
  } else {
    let mut changes = 0
    let mut previous = values[1] - values[0]
    for index in 2.. 0.0) != (previous > 0.0) {
        changes += 1
      }
      if current != 0.0 {
        previous = current
      }
    }
    changes
  }
}