///|
/// Measured properties of a timing curve over a normalized interval.
pub struct CurveStatistics {
  minimum : Double
  maximum : Double
  mean : Double
  integral : Double
  peak_velocity : Double
  peak_acceleration : Double
  total_variation : Double
  overshoot : Double
  undershoot : Double
} derive(Debug)

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

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

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

///|
pub fn CurveStatistics::integral(self : CurveStatistics) -> Double {
  self.integral
}

///|
pub fn CurveStatistics::peak_velocity(self : CurveStatistics) -> Double {
  self.peak_velocity
}

///|
pub fn CurveStatistics::peak_acceleration(self : CurveStatistics) -> Double {
  self.peak_acceleration
}

///|
pub fn CurveStatistics::total_variation(self : CurveStatistics) -> Double {
  self.total_variation
}

///|
pub fn CurveStatistics::overshoot(self : CurveStatistics) -> Double {
  self.overshoot
}

///|
pub fn CurveStatistics::undershoot(self : CurveStatistics) -> Double {
  self.undershoot
}

///|
pub fn analyze_curve(curve : Curve, samples : Int) -> CurveStatistics {
  let count = samples.max(4)
  let step = 1.0 / count.to_double()
  let first = curve.apply(0.0)
  let mut minimum = first
  let mut maximum = first
  let mut integral = 0.0
  let mut variation = 0.0
  let mut peak_velocity = 0.0
  let mut peak_acceleration = 0.0
  let mut previous = first
  let mut previous_velocity = 0.0
  for index in 1..<=count {
    let current_time = index.to_double() * step
    let current = curve.apply(current_time)
    let velocity = (current - previous) / step
    let acceleration = (velocity - previous_velocity) / step
    minimum = minimum.min(current)
    maximum = maximum.max(current)
    integral = integral + (previous + current) * step * 0.5
    variation = variation + (current - previous).abs()
    peak_velocity = peak_velocity.max(velocity.abs())
    peak_acceleration = peak_acceleration.max(acceleration.abs())
    previous = current
    previous_velocity = velocity
  }
  {
    minimum,
    maximum,
    mean: integral,
    integral,
    peak_velocity,
    peak_acceleration,
    total_variation: variation,
    overshoot: (maximum - 1.0).max(0.0),
    undershoot: (0.0 - minimum).max(0.0),
  }
}

///|
pub fn CurveStatistics::monotonic(self : CurveStatistics) -> Bool {
  self.total_variation <= (self.maximum - self.minimum).abs() + 0.000001
}

///|
pub fn CurveStatistics::range(self : CurveStatistics) -> Double {
  self.maximum - self.minimum
}

///|
pub fn CurveStatistics::normalized_overshoot(self : CurveStatistics) -> Double {
  let range = self.range().abs()
  if range == 0.0 {
    0.0
  } else {
    self.overshoot / range
  }
}

///|
pub struct CurveComparison {
  mean_absolute_error : Double
  root_mean_square_error : Double
  maximum_error : Double
  area_error : Double
  correlation : Double
  samples : Int
} derive(Debug)

///|
pub fn CurveComparison::mean_absolute_error(self : CurveComparison) -> Double {
  self.mean_absolute_error
}

///|
pub fn CurveComparison::root_mean_square_error(
  self : CurveComparison,
) -> Double {
  self.root_mean_square_error
}

///|
pub fn CurveComparison::maximum_error(self : CurveComparison) -> Double {
  self.maximum_error
}

///|
pub fn CurveComparison::area_error(self : CurveComparison) -> Double {
  self.area_error
}

///|
pub fn CurveComparison::correlation(self : CurveComparison) -> Double {
  self.correlation
}

///|
pub fn CurveComparison::samples(self : CurveComparison) -> Int {
  self.samples
}

///|
pub fn compare_curves(
  first : Curve,
  second : Curve,
  samples : Int,
) -> CurveComparison {
  let count = samples.max(2)
  let step = 1.0 / (count - 1).to_double()
  let mut absolute = 0.0
  let mut square = 0.0
  let mut maximum = 0.0
  let mut area = 0.0
  let mut first_mean = 0.0
  let mut second_mean = 0.0
  for index in 0.. 0 {
      let prior_time = (index - 1).to_double() * step
      area = area +
        (
          (first.apply(prior_time) - second.apply(prior_time)).abs() +
          difference.abs()
        ) *
        step *
        0.5
    }
    first_mean = first_mean + left
    second_mean = second_mean + right
  }
  first_mean = first_mean / count.to_double()
  second_mean = second_mean / count.to_double()
  let mut covariance = 0.0
  let mut first_variance = 0.0
  let mut second_variance = 0.0
  for index in 0.. Array[CurveFeature] {
  [
    FeatureStart,
    FeatureEnd,
    FeaturePeakVelocity,
    FeaturePeakAcceleration,
    FeatureFirstOvershoot,
    FeatureSettlingTime,
  ]
}

///|
pub fn curve_feature_value(
  curve : Curve,
  feature : CurveFeature,
  samples : Int,
) -> Double {
  let count = samples.max(4)
  let stats = analyze_curve(curve, count)
  match feature {
    FeatureStart => curve.apply(0.0)
    FeatureEnd => curve.apply(1.0)
    FeaturePeakVelocity => stats.peak_velocity
    FeaturePeakAcceleration => stats.peak_acceleration
    FeatureFirstOvershoot => stats.overshoot
    FeatureSettlingTime => {
      let mut result = 1.0
      for index in 0..<=count {
        let time = index.to_double() / count.to_double()
        if (curve.apply(time) - 1.0).abs() <= 0.01 && result == 1.0 {
          result = time
        }
      }
      result
    }
  }
}

///|
pub struct CurveEnvelope {
  lower : Array[Double]
  upper : Array[Double]
  samples : Int
} derive(Debug)

///|
pub fn build_envelope(curves : Array[Curve], samples : Int) -> CurveEnvelope {
  let count = samples.max(2)
  let lower : Array[Double] = []
  let upper : Array[Double] = []
  for index in 0.. Array[Double] {
  self.lower.copy()
}

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

///|
pub fn CurveEnvelope::samples(self : CurveEnvelope) -> Int {
  self.samples
}

///|
pub fn CurveEnvelope::contains(self : CurveEnvelope, curve : Curve) -> Bool {
  for index in 0.. self.upper[index] + 0.000001 {
      return false
    }
  }
  true
}

///|
pub fn CurveEnvelope::width_at(self : CurveEnvelope, time : Double) -> Double {
  let position = clamp01(time) * (self.samples - 1).to_double()
  let index = @math.floor(position).to_int().min(self.samples - 2)
  let ratio = position - index.to_double()
  let lower = self.lower[index] +
    (self.lower[index + 1] - self.lower[index]) * ratio
  let upper = self.upper[index] +
    (self.upper[index + 1] - self.upper[index]) * ratio
  upper - lower
}

///|
pub fn estimate_settling_time(
  curve : Curve,
  tolerance : Double,
  samples : Int,
) -> Double {
  let count = samples.max(2)
  let allowed = tolerance.abs()
  let mut latest = 0.0
  for index in 0.. allowed {
      latest = time
    }
  }
  if latest >= 1.0 {
    1.0
  } else {
    latest
  }
}

///|
pub fn estimate_zero_crossings(curve : Curve, samples : Int) -> Int {
  let count = samples.max(2)
  let mut previous = curve.apply(0.0)
  let mut crossings = 0
  for index in 1..= 0.0) || (previous >= 0.0 && current < 0.0) {
      crossings = crossings + 1
    }
    previous = current
  }
  crossings
}