///|
/// Control limits for reliability-monitoring streams.
pub struct ControlLimits {
  center : Double
  upper : Double
  lower : Double
  sigma : Double
  violations : Array[Int]
}

///|
pub fn control_limits(
  center~ : Double,
  upper~ : Double,
  lower~ : Double,
  sigma~ : Double,
  violations~ : Array[Int],
) -> ControlLimits {
  { center, upper, lower, sigma, violations }
}

///|
pub fn shewhart_limits(
  values : Array[Double],
  sigma_multiplier : Double,
) -> ControlLimits {
  let center = mean(values)
  let sigma = variance(values).sqrt()
  let upper = center + sigma_multiplier * sigma
  let lower = center - sigma_multiplier * sigma
  let violations = values.foldi(init=[], (i, result, value) => {
    if value > upper || value < lower {
      result.push(i)
    }
    result
  })
  control_limits(center~, upper~, lower~, sigma~, violations~)
}

///|
pub fn ewma(
  values : Array[Double],
  lambda : Double,
  target : Double,
) -> Array[Double] {
  if lambda <= 0.0 || lambda > 1.0 {
    abort("EWMA lambda must be in (0, 1]")
  }
  if values.is_empty() {
    return []
  }
  let result = Array::make(values.length(), target)
  let mut state = target
  for i in 0.. (Array[Double], Array[Double]) {
  let positive = Array::make(values.length(), 0.0)
  let negative = Array::make(values.length(), 0.0)
  for i in 0.. (Double, Double, Double) {
  let center = mean(values)
  let sigma = variance(values).sqrt()
  if sigma == 0.0 {
    return (1.0e300, 1.0e300, 1.0e300)
  }
  let cpu = (upper_spec - center) / (3.0 * sigma)
  let cpl = (center - lower_spec) / (3.0 * sigma)
  (cpu.min(cpl), cpu, cpl)
}

///|
pub fn defect_rate(defects : Int, opportunities : Int) -> MetricEstimate {
  if defects < 0 || opportunities <= 0 || defects > opportunities {
    abort("invalid defect counts")
  }
  let rate = defects.to_double() / opportunities.to_double()
  let error = 1.96 * (rate * (1.0 - rate) / opportunities.to_double()).sqrt()
  metric_estimate(
    estimate=rate,
    lower=(rate - error).max(0.0),
    upper=(rate + error).min(1.0),
    confidence_level=0.95,
  )
}

///|
pub fn poisson_rate(count : Int, exposure : Double) -> MetricEstimate {
  if count < 0 || exposure <= 0.0 {
    abort("invalid Poisson rate input")
  }
  let rate = count.to_double() / exposure
  let error = 1.96 * rate.max(1.0 / exposure).sqrt() / exposure.sqrt()
  metric_estimate(
    estimate=rate,
    lower=(rate - error).max(0.0),
    upper=rate + error,
    confidence_level=0.95,
  )
}

///|
pub fn run_rules(
  values : Array[Double],
  limits : ControlLimits,
) -> Array[String] {
  let result : Array[String] = []
  for i in 0.. limits.upper {
      result.push("point \{i}: above upper limit")
    }
    if values[i] < limits.lower {
      result.push("point \{i}: below lower limit")
    }
    if i >= 2 &&
      values[i] > limits.center &&
      values[i - 1] > limits.center &&
      values[i - 2] > limits.center {
      result.push("points \{i - 2}-\{i}: run above center")
    }
  }
  result
}