///|
pub struct Statistics {
  count : Int
  minimum : Float
  maximum : Float
  mean : Float
  variance : Float
  standard_deviation : Float
  sum : Float
} derive(Debug, Eq)

///|
pub fn mean(values : Array[Float]) -> Float? {
  if values.length() == 0 {
    None
  } else {
    let mut total : Float = 0.0
    for value in values {
      total = total + value
    }
    Some(total / Float::from_int(values.length()))
  }
}

///|
pub fn summarize(values : Array[Float]) -> Statistics {
  if values.length() == 0 {
    {
      count: 0,
      minimum: 0.0,
      maximum: 0.0,
      mean: 0.0,
      variance: 0.0,
      standard_deviation: 0.0,
      sum: 0.0,
    }
  } else {
    let mut total : Float = 0.0
    let mut minimum = values[0]
    let mut maximum = values[0]
    for value in values {
      total = total + value
      if value < minimum {
        minimum = value
      }
      if value > maximum {
        maximum = value
      }
    }
    let average : Float = total / Float::from_int(values.length())
    let mut squared : Float = 0.0
    for value in values {
      let delta = value - average
      squared = squared + delta * delta
    }
    let variance : Float = squared / Float::from_int(values.length())
    {
      count: values.length(),
      minimum,
      maximum,
      mean: average,
      variance,
      standard_deviation: variance.sqrt(),
      sum: total,
    }
  }
}

///|
pub fn percentile(values : Array[Float], percentile : Float) -> Float? {
  if values.length() == 0 || percentile < 0.0 || percentile > 100.0 {
    return None
  }
  let sorted = values.copy()
  sorted.sort()
  let position : Float = percentile /
    100.0 *
    Float::from_int(sorted.length() - 1)
  let lower = position.floor().to_int()
  let upper = position.ceil().to_int()
  if lower == upper {
    Some(sorted[lower])
  } else {
    let fraction = position - Float::from_int(lower)
    Some(sorted[lower] + (sorted[upper] - sorted[lower]) * fraction)
  }
}

///|
pub fn moving_average(values : Array[Float], window : Int) -> Array[Float] {
  let result : Array[Float] = []
  if window <= 0 {
    return result
  }
  for index in 0.. ObservationFlag {
  NormalObservation
}

///|
pub fn above_control_limit() -> ObservationFlag {
  AboveControlLimit
}

///|
pub fn below_control_limit() -> ObservationFlag {
  BelowControlLimit
}

///|
pub struct ControlLimits {
  center : Float
  upper : Float
  lower : Float
} derive(Debug, Eq)

///|
pub fn control_limits(values : Array[Float]) -> ControlLimits {
  let summary = summarize(values)
  let span : Float = 3.0 * summary.standard_deviation
  {
    center: summary.mean,
    upper: summary.mean + span,
    lower: summary.mean - span,
  }
}

///|
pub fn ControlLimits::flag(
  self : ControlLimits,
  values : Array[Float],
) -> Array[ObservationFlag] {
  let result : Array[ObservationFlag] = []
  for value in values {
    if value > self.upper {
      result.push(AboveControlLimit)
    } else if value < self.lower {
      result.push(BelowControlLimit)
    } else {
      result.push(NormalObservation)
    }
  }
  result
}

///|
pub fn ControlLimits::contains(self : ControlLimits, value : Float) -> Bool {
  value >= self.lower && value <= self.upper
}

///|
pub fn weighted_mean(values : Array[Float], weights : Array[Float]) -> Float? {
  if values.length() == 0 || values.length() != weights.length() {
    return None
  }
  let mut numerator : Float = 0.0
  let mut denominator : Float = 0.0
  for index, value in values {
    numerator = numerator + value * weights[index]
    denominator = denominator + weights[index]
  }
  if denominator == 0.0 {
    None
  } else {
    Some(numerator / denominator)
  }
}

///|
pub fn coefficient_of_variation(values : Array[Float]) -> Float? {
  let summary = summarize(values)
  if summary.count == 0 || summary.mean == 0.0 {
    None
  } else {
    Some(summary.standard_deviation / summary.mean)
  }
}

///|
pub fn clamp(value : Float, minimum : Float, maximum : Float) -> Float {
  if value < minimum {
    minimum
  } else if value > maximum {
    maximum
  } else {
    value
  }
}