///|
pub struct WindowedAnomalyDetector {
  window : StreamingWindow
  threshold : Double
  mut total_seen : Int
  mut total_anomalies : Int
}

///|
pub fn WindowedAnomalyDetector::new(
  capacity : Int,
  threshold? : Double = 3.5,
) -> WindowedAnomalyDetector {
  if threshold <= 0.0 {
    abort("threshold must be positive")
  }
  {
    window: StreamingWindow::new(capacity),
    threshold,
    total_seen: 0,
    total_anomalies: 0,
  }
}

///|
pub fn WindowedAnomalyDetector::observe(
  self : WindowedAnomalyDetector,
  value : Double,
) -> Bool {
  let values = self.window.values()
  let center = median(values)
  let scale = mad(values)
  let score = robust_z_score(value, center, scale)
  let zero_scale_anomaly = scale == 0.0 &&
    values.length() > 2 &&
    value != center
  let anomaly = self.window.len() > 2 &&
    (zero_scale_anomaly || abs_double(score) > self.threshold)
  self.window.push(value)
  self.total_seen += 1
  if anomaly {
    self.total_anomalies += 1
  }
  anomaly
}

///|
pub fn WindowedAnomalyDetector::observe_many(
  self : WindowedAnomalyDetector,
  data : Array[Double],
) -> Array[Bool] {
  let result = []
  for value in data {
    result.push(self.observe(value))
  }
  result
}

///|
pub fn WindowedAnomalyDetector::window_values(
  self : WindowedAnomalyDetector,
) -> Array[Double] {
  self.window.values()
}

///|
pub fn WindowedAnomalyDetector::seen(self : WindowedAnomalyDetector) -> Int {
  self.total_seen
}

///|
pub fn WindowedAnomalyDetector::anomalies(
  self : WindowedAnomalyDetector,
) -> Int {
  self.total_anomalies
}

///|
pub fn WindowedAnomalyDetector::rate(self : WindowedAnomalyDetector) -> Double {
  if self.total_seen == 0 {
    0.0
  } else {
    self.total_anomalies.to_double() / self.total_seen.to_double()
  }
}

///|
pub fn WindowedAnomalyDetector::reset(self : WindowedAnomalyDetector) -> Unit {
  self.window.clear()
  self.total_seen = 0
  self.total_anomalies = 0
}

///|
pub struct AdaptiveClipper {
  mut center : Double
  mut scale : Double
  alpha : Double
  threshold : Double
  mut initialized : Bool
}

///|
pub fn AdaptiveClipper::new(
  alpha : Double,
  threshold? : Double = 3.5,
) -> AdaptiveClipper {
  if alpha <= 0.0 || alpha > 1.0 {
    abort("alpha must be in (0, 1]")
  }
  if threshold <= 0.0 {
    abort("threshold must be positive")
  }
  { center: 0.0, scale: 0.0, alpha, threshold, initialized: false }
}

///|
pub fn AdaptiveClipper::update(
  self : AdaptiveClipper,
  value : Double,
) -> Double {
  if !self.initialized {
    self.center = value
    self.scale = 0.0
    self.initialized = true
    return value
  }
  let deviation = value - self.center
  let limit = if self.scale == 0.0 {
    self.threshold
  } else {
    self.threshold * self.scale
  }
  let clipped = self.center + clamp_double(deviation, -limit, limit)
  self.center = self.center + self.alpha * (clipped - self.center)
  self.scale = self.scale +
    self.alpha * (abs_double(clipped - self.center) - self.scale)
  clipped
}

///|
pub fn AdaptiveClipper::transform(
  self : AdaptiveClipper,
  data : Array[Double],
) -> Array[Double] {
  let result = []
  for value in data {
    result.push(self.update(value))
  }
  result
}

///|
pub fn AdaptiveClipper::location(self : AdaptiveClipper) -> Double {
  self.center
}

///|
pub fn AdaptiveClipper::scale(self : AdaptiveClipper) -> Double {
  self.scale
}

///|
pub fn AdaptiveClipper::is_initialized(self : AdaptiveClipper) -> Bool {
  self.initialized
}

///|
pub fn robust_online_mean(data : Array[Double], clip_limit : Double) -> Double {
  let state = StreamingRobustStats::new(clip_limit)
  state.push_many(data)
  state.mean()
}

///|
pub fn robust_online_variance(
  data : Array[Double],
  clip_limit : Double,
) -> Double {
  let state = StreamingRobustStats::new(clip_limit)
  state.push_many(data)
  state.variance()
}

///|
pub fn online_covariance(x : Array[Double], y : Array[Double]) -> Double {
  let state = StreamingCovariance::new()
  for index = 0; index < x.length() && index < y.length(); index = index + 1 {
    state.push(x[index], y[index])
  }
  state.covariance()
}

///|
pub fn online_correlation(x : Array[Double], y : Array[Double]) -> Double {
  let state = StreamingCovariance::new()
  for index = 0; index < x.length() && index < y.length(); index = index + 1 {
    state.push(x[index], y[index])
  }
  state.correlation()
}