///|
/// Root mean square of finite signal values.
pub fn root_mean_square(values : Array[Double]) -> Double {
  let mut sum = 0.0
  let mut count = 0
  for value in values {
    if !value.is_nan() && !value.is_inf() {
      sum = sum + value * value
      count = count + 1
    }
  }
  if count == 0 {
    0.0
  } else {
    (sum / count.to_double()).sqrt()
  }
}

///|
/// Coefficient of variation, expressed as a unitless ratio.
pub fn coefficient_of_variation(values : Array[Double]) -> Double {
  let mean = compute_mean(values)
  if mean.abs() < 0.000001 {
    0.0
  } else {
    compute_std_dev(values) / mean.abs()
  }
}

///|
/// Causal rolling mean with a shrinking prefix window.
pub fn rolling_mean(values : Array[Double], window_size : Int) -> Array[Double] {
  let n = values.length()
  if n == 0 {
    return []
  }
  let window = if window_size <= 1 { 1 } else { window_size }
  let result : Array[Double] = Array::new(capacity=n)
  for i = 0; i < n; i = i + 1 {
    let start = if i + 1 > window { i + 1 - window } else { 0 }
    result.push(mean_in_range(values, start, i + 1))
  }
  result
}

///|
/// Normalized autocorrelation at a non-negative integer lag.
pub fn autocorrelation(values : Array[Double], lag : Int) -> Double {
  let n = values.length()
  if lag < 0 || lag >= n {
    return 0.0
  }
  let mean = compute_mean(values)
  let mut denominator = 0.0
  let mut numerator = 0.0
  for i = 0; i < n; i = i + 1 {
    let centered = values[i] - mean
    denominator = denominator + centered * centered
    if i + lag < n {
      numerator = numerator + centered * (values[i + lag] - mean)
    }
  }
  if denominator < 0.000001 {
    if lag == 0 {
      1.0
    } else {
      0.0
    }
  } else {
    numerator / denominator
  }
}

///|
/// Linearly maps finite values to [0, 1]. Constant signals map to zero.
pub fn normalize_minmax(values : Array[Double]) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  let finite : Array[Double] = []
  for value in values {
    if !value.is_nan() && !value.is_inf() {
      finite.push(value)
    }
  }
  if finite.length() == 0 {
    let zeros : Array[Double] = []
    for _ in values {
      zeros.push(0.0)
    }
    return zeros
  }
  let min_value = finite[0]
  let max_value = finite[0]
  let mut lo = min_value
  let mut hi = max_value
  for value in finite {
    if value < lo {
      lo = value
    }
    if value > hi {
      hi = value
    }
  }
  let span = hi - lo
  let result : Array[Double] = []
  if span.abs() < 0.000001 {
    for _ in values {
      result.push(0.0)
    }
  } else {
    for value in values {
      if value.is_nan() || value.is_inf() {
        result.push(0.0)
      } else {
        result.push((value - lo) / span)
      }
    }
  }
  result
}

///|
/// Converts a finite signal into z-scores; degenerate signals map to zero.
pub fn zscore(values : Array[Double]) -> Array[Double] {
  let mean = compute_mean(values)
  let sd = compute_std_dev(values)
  let result : Array[Double] = []
  for value in values {
    if sd < 0.000001 || value.is_nan() || value.is_inf() {
      result.push(0.0)
    } else {
      result.push((value - mean) / sd)
    }
  }
  result
}