///|
/// A bounded feature window for streaming sensor preprocessing.
pub struct RollingWindow {
  capacity : Int
  mut values : Array[Double]
}

///|
pub fn RollingWindow::new(capacity : Int) -> RollingWindow {
  { capacity: if capacity < 1 { 1 } else { capacity }, values: [] }
}

///|
pub fn RollingWindow::push(self : RollingWindow, value : Double) -> Unit {
  self.values.push(value)
  if self.values.length() > self.capacity {
    let trimmed = self.values[self.values.length() - self.capacity:].to_owned()
    self.values = trimmed
  }
}

///|
pub fn RollingWindow::length(self : RollingWindow) -> Int {
  self.values.length()
}

///|
pub fn RollingWindow::capacity(self : RollingWindow) -> Int {
  self.capacity
}

///|
pub fn RollingWindow::values(self : RollingWindow) -> Array[Double] {
  self.values.copy()
}

///|
pub fn RollingWindow::mean(self : RollingWindow) -> Double {
  vector_mean(self.values)
}

///|
pub fn RollingWindow::variance(self : RollingWindow) -> Double {
  vector_variance(self.values)
}

///|
pub fn RollingWindow::minimum(self : RollingWindow) -> Double {
  if self.values.length() == 0 {
    return 0.0
  }
  let mut result = self.values[0]
  for value in self.values {
    if value < result {
      result = value
    }
  }
  result
}

///|
pub fn RollingWindow::maximum(self : RollingWindow) -> Double {
  if self.values.length() == 0 {
    return 0.0
  }
  let mut result = self.values[0]
  for value in self.values {
    if value > result {
      result = value
    }
  }
  result
}

///|
pub fn signal_difference(
  values : Array[Double],
  spacing : Double,
) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  if values.length() == 1 {
    return [0.0]
  }
  let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
  let result = Array::make(values.length(), 0.0)
  result[0] = (values[1] - values[0]) / safe_spacing
  for i in 1.. Array[Double] {
  let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
  let result = Array::make(values.length(), 0.0)
  for i in 1.. Array[Double] {
  if values.length() == 0 {
    return []
  }
  let size = if window < 1 { 1 } else { window }
  Array::makei(values.length(), i => {
    let start = if i + 1 > size { i + 1 - size } else { 0 }
    vector_mean(values[start:i + 1].to_owned())
  })
}

///|
pub fn rolling_variance(values : Array[Double], window : Int) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  let size = if window < 1 { 1 } else { window }
  Array::makei(values.length(), i => {
    let start = if i + 1 > size { i + 1 - size } else { 0 }
    vector_variance(values[start:i + 1].to_owned())
  })
}

///|
pub fn linear_slope(values : Array[Double], spacing : Double) -> Double {
  if values.length() < 2 {
    return 0.0
  }
  let safe_spacing = if spacing <= 0.0 { 1.0 } else { spacing }
  let x_mean = (values.length() - 1).to_double() * 0.5
  let y_mean = vector_mean(values)
  let mut numerator = 0.0
  let mut denominator = 0.0
  for i, value in values {
    let x = i.to_double()
    numerator = numerator + (x - x_mean) * (value - y_mean)
    denominator = denominator + (x - x_mean) * (x - x_mean)
  }
  if denominator <= 0.0 {
    0.0
  } else {
    numerator / denominator / safe_spacing
  }
}

///|
pub fn signal_correlation(
  left : Array[Double],
  right : Array[Double],
) -> Double {
  if left.length() != right.length() || left.length() < 2 {
    return 0.0
  }
  let centered_left = vector_mean_center(left)
  let centered_right = vector_mean_center(right)
  let denominator = vector_l2_norm(centered_left) *
    vector_l2_norm(centered_right)
  if denominator <= 0.000000000001 {
    0.0
  } else {
    vector_dot(centered_left, centered_right) / denominator
  }
}

///|
pub fn signal_autocorrelation(values : Array[Double], lag : Int) -> Double {
  if lag < 0 || lag >= values.length() {
    return 0.0
  }
  let left = values[lag:].to_owned()
  let right = values[:values.length() - lag].to_owned()
  signal_correlation(left, right)
}

///|
pub fn detect_spikes(
  values : Array[Double],
  threshold : Double,
  radius : Int,
) -> Array[Bool] {
  if values.length() == 0 {
    return []
  }
  let safe_threshold = if threshold < 0.0 { 0.0 } else { threshold }
  let safe_radius = if radius < 1 { 1 } else { radius }
  Array::makei(values.length(), index => {
    let start = if index > safe_radius { index - safe_radius } else { 0 }
    let end = if index + safe_radius + 1 > values.length() {
      values.length()
    } else {
      index + safe_radius + 1
    }
    let window : Array[Double] = []
    for i in start.. safe_threshold
    }
  })
}

///|
pub fn interpolate_missing(
  values : Array[Double?],
  fallback : Double,
) -> Array[Double] {
  let result = Array::make(values.length(), fallback)
  let mut last_index = -1
  let mut last_value = fallback
  for i, value in values {
    match value {
      Some(current) => {
        if last_index < 0 {
          for j in 0.. ()
    }
  }
  if last_index >= 0 {
    for i in (last_index + 1).. Array[Double] {
  if factor <= 0 {
    return []
  }
  let result : Array[Double] = []
  for i, value in values {
    if i % factor == 0 {
      result.push(value)
    }
  }
  result
}

///|
pub fn resample_linear(
  values : Array[Double],
  output_length : Int,
) -> Array[Double] {
  if output_length <= 0 || values.length() == 0 {
    return []
  }
  if values.length() == 1 {
    return Array::make(output_length, values[0])
  }
  if output_length == 1 {
    return [values[0]]
  }
  let result = Array::make(output_length, 0.0)
  for i in 0..= values.length() { lower } else { lower + 1 }
    let amount = position - lower.to_double()
    result[i] = values[lower] + amount * (values[upper] - values[lower])
  }
  result
}

///|
pub fn normalize_signal(values : Array[Double]) -> Array[Double] {
  let mean = vector_mean(values)
  let deviation = vector_variance(values).sqrt()
  if deviation <= 0.000000000001 {
    return values.map(_ => 0.0)
  }
  values.map(value => (value - mean) / deviation)
}

///|
pub fn hampel_filter(
  values : Array[Double],
  radius : Int,
  threshold : Double,
) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  let safe_radius = if radius < 1 { 1 } else { radius }
  let safe_threshold = if threshold <= 0.0 { 3.0 } else { threshold }
  Array::makei(values.length(), index => {
    let start = if index > safe_radius { index - safe_radius } else { 0 }
    let end = if index + safe_radius + 1 > values.length() {
      values.length()
    } else {
      index + safe_radius + 1
    }
    let window = values[start:end].to_owned()
    let median = vector_median(window)
    let deviation = vector_mad(window)
    if deviation <= 0.000000000001 {
      if (values[index] - median).abs() > 0.000000000001 {
        median
      } else {
        values[index]
      }
    } else if (values[index] - median).abs() <= safe_threshold * deviation {
      values[index]
    } else {
      median
    }
  })
}

///|
pub struct FeatureVector {
  mean : Double
  variance : Double
  slope : Double
  minimum : Double
  maximum : Double
  energy : Double
} derive(Debug)

///|
pub fn FeatureVector::mean(self : FeatureVector) -> Double {
  self.mean
}

///|
pub fn FeatureVector::variance(self : FeatureVector) -> Double {
  self.variance
}

///|
pub fn FeatureVector::slope(self : FeatureVector) -> Double {
  self.slope
}

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

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

///|
pub fn FeatureVector::energy(self : FeatureVector) -> Double {
  self.energy
}

///|
pub fn extract_features(
  values : Array[Double],
  spacing : Double,
) -> FeatureVector {
  let mut energy = 0.0
  for value in values {
    energy = energy + value * value
  }
  {
    mean: vector_mean(values),
    variance: vector_variance(values),
    slope: linear_slope(values, spacing),
    minimum: if values.length() == 0 {
      0.0
    } else {
      vector_quantile(values, 0.0)
    },
    maximum: if values.length() == 0 {
      0.0
    } else {
      vector_quantile(values, 1.0)
    },
    energy,
  }
}