///|
/// Returns the population covariance of two equally sized signals.
///
/// Covariance is useful for comparing sensor channels without requiring a
/// frequency-domain representation. The divisor is `N`, matching the other
/// descriptive statistics in this package.
pub fn covariance(
  signal : ArrayView[Double],
  other : ArrayView[Double],
) -> Double raise SpectrumError {
  if signal.length() == 0 {
    raise EmptySignal
  }
  if signal.length() != other.length() {
    raise LengthMismatch
  }
  let mut mean_signal = 0.0
  let mut mean_other = 0.0
  for i in 0.. Double raise SpectrumError {
  if signal.length() == 0 {
    raise EmptySignal
  }
  if lag < 0 || lag >= signal.length() {
    raise InvalidArgument(message="lag must be in [0, signal length)")
  }
  let mean = signal_stats(signal).mean
  let count = signal.length() - lag
  let mut total = 0.0
  for i in 0.. Double raise SpectrumError {
  if signal.length() == 0 {
    raise EmptySignal
  }
  let mean = signal_stats(signal).mean
  let mut total = 0.0
  for value in signal {
    total = total + (value - mean).abs()
  }
  total / Double::from_int(signal.length())
}

///|
/// Returns the interquartile range using the package's linear quantile rule.
pub fn interquartile_range(
  signal : ArrayView[Double],
) -> Double raise SpectrumError {
  quantile(signal, 0.75) - quantile(signal, 0.25)
}

///|
/// Clamps every sample to the inclusive interval `[lower, upper]`.
pub fn clip_signal(
  signal : ArrayView[Double],
  lower : Double,
  upper : Double,
) -> Array[Double] raise SpectrumError {
  if signal.length() == 0 {
    raise EmptySignal
  }
  if lower > upper {
    raise InvalidArgument(message="lower must not exceed upper")
  }
  let out : Array[Double] = []
  for value in signal {
    out.push(
      if value < lower {
        lower
      } else if value > upper {
        upper
      } else {
        value
      },
    )
  }
  out
}

///|
/// Returns sample indices where a signal crosses a threshold in either direction.
pub fn threshold_crossings(
  signal : ArrayView[Double],
  threshold : Double,
) -> Array[Int] raise SpectrumError {
  if signal.length() < 2 {
    raise InvalidArgument(message="threshold crossings need two samples")
  }
  let crossings : Array[Int] = []
  for i in 1.. Array[Int] raise SpectrumError {
  if signal.length() < 2 {
    raise InvalidArgument(message="zero crossing indices need two samples")
  }
  let crossings : Array[Int] = []
  for i in 1.. 0.0) ||
      (signal[i - 1] > 0.0 && signal[i] < 0.0) {
      crossings.push(i)
    }
  }
  crossings
}