///|
/// Discrete constraint solver for a sampled scalar motion signal. It performs
/// forward and backward passes so an edit can honour limits without changing
/// the number of frames requested by the renderer.
pub struct ConstraintSolveConfig {
  frame_rate : Double
  limits : MotionLimitProfile2
  preserve_endpoints : Bool
  passes : Int
} derive(Debug)

///|
pub fn constraint_solve_config(
  frame_rate : Double,
  limits : MotionLimitProfile2,
) -> ConstraintSolveConfig {
  {
    frame_rate: frame_rate.max(0.001),
    limits,
    preserve_endpoints: true,
    passes: 3,
  }
}

///|
pub fn ConstraintSolveConfig::with_endpoints(
  self : ConstraintSolveConfig,
  preserve : Bool,
) -> ConstraintSolveConfig {
  {
    frame_rate: self.frame_rate,
    limits: self.limits,
    preserve_endpoints: preserve,
    passes: self.passes,
  }
}

///|
pub fn ConstraintSolveConfig::with_passes(
  self : ConstraintSolveConfig,
  passes : Int,
) -> ConstraintSolveConfig {
  {
    frame_rate: self.frame_rate,
    limits: self.limits,
    preserve_endpoints: self.preserve_endpoints,
    passes: passes.max(1).min(32),
  }
}

///|
pub fn ConstraintSolveConfig::frame_rate(
  self : ConstraintSolveConfig,
) -> Double {
  self.frame_rate
}

///|
pub fn ConstraintSolveConfig::limits(
  self : ConstraintSolveConfig,
) -> MotionLimitProfile2 {
  self.limits
}

///|
pub struct ConstraintSolveResult {
  values : Array[Double]
  velocities : Array[Double]
  accelerations : Array[Double]
  maximum_error : Double
  iterations : Int
  converged : Bool
} derive(Debug)

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

///|
pub fn ConstraintSolveResult::velocities(
  self : ConstraintSolveResult,
) -> Array[Double] {
  self.velocities.copy()
}

///|
pub fn ConstraintSolveResult::accelerations(
  self : ConstraintSolveResult,
) -> Array[Double] {
  self.accelerations.copy()
}

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

///|
pub fn ConstraintSolveResult::iterations(self : ConstraintSolveResult) -> Int {
  self.iterations
}

///|
pub fn ConstraintSolveResult::converged(self : ConstraintSolveResult) -> Bool {
  self.converged
}

///|
fn solve_pass(
  values : Array[Double],
  target : Array[Double],
  config : ConstraintSolveConfig,
  forward : Bool,
) -> Double {
  if values.length() < 2 {
    return 0.0
  }
  let dt = 1.0 / config.frame_rate
  let mut maximum_error = 0.0
  if forward {
    for index in 1.. 1 { values[index - 2] } else { previous }
      let result = constrain_scalar_step(
        target[index],
        previous,
        (previous - prior) / dt,
        0.0,
        dt,
        config.limits,
      )
      let error = (values[index] - result.value).abs()
      maximum_error = maximum_error.max(error)
      values[index] = result.value
    }
  } else {
    let mut index = values.length() - 2
    while index >= 0 {
      let next = values[index + 1]
      let after = if index + 2 < values.length() {
        values[index + 2]
      } else {
        next
      }
      let result = constrain_scalar_step(
        target[index],
        next,
        (next - after) / dt,
        0.0,
        dt,
        config.limits,
      )
      let error = (values[index] - result.value).abs()
      maximum_error = maximum_error.max(error)
      values[index] = result.value
      index = index - 1
    }
  }
  maximum_error
}

///|
pub fn solve_constrained_signal(
  values : Array[Double],
  config : ConstraintSolveConfig,
) -> ConstraintSolveResult {
  if values.length() == 0 {
    return {
      values: [],
      velocities: [],
      accelerations: [],
      maximum_error: 0.0,
      iterations: 0,
      converged: true,
    }
  }
  let target = values.copy()
  let solved = values.copy()
  let mut maximum_error = 0.0
  let mut iteration = 0
  let mut converged = false
  while iteration < config.passes && !converged {
    let forward_error = solve_pass(solved, target, config, true)
    let backward_error = solve_pass(solved, target, config, false)
    maximum_error = forward_error.max(backward_error)
    if config.preserve_endpoints {
      solved[0] = target[0]
      solved[solved.length() - 1] = target[target.length() - 1]
    }
    converged = maximum_error <= config.limits.tolerance
    iteration = iteration + 1
  }
  let dt = 1.0 / config.frame_rate
  let velocities : Array[Double] = []
  let accelerations : Array[Double] = []
  for index in 0.. Array[Point2] {
  let x : Array[Double] = []
  let y : Array[Double] = []
  for point in values {
    x.push(point.x())
    y.push(point.y())
  }
  let solved_x = solve_constrained_signal(x, config).values
  let solved_y = solve_constrained_signal(y, config).values
  let result : Array[Point2] = []
  for index in 0.. Array[Point3] {
  let x : Array[Double] = []
  let y : Array[Double] = []
  let z : Array[Double] = []
  for point in values {
    x.push(point.x())
    y.push(point.y())
    z.push(point.z())
  }
  let solved_x = solve_constrained_signal(x, config).values
  let solved_y = solve_constrained_signal(y, config).values
  let solved_z = solve_constrained_signal(z, config).values
  let result : Array[Point3] = []
  for index in 0.. Int {
  self.removed
}

///|
pub fn KeyframeCleanupReport::merged(self : KeyframeCleanupReport) -> Int {
  self.merged
}

///|
pub fn KeyframeCleanupReport::adjusted(self : KeyframeCleanupReport) -> Int {
  self.adjusted
}

///|
pub fn KeyframeCleanupReport::maximum_adjustment(
  self : KeyframeCleanupReport,
) -> Double {
  self.maximum_adjustment
}

///|
pub fn cleanup_samples(
  values : Array[Double],
  tolerance : Double,
) -> (Array[Double], KeyframeCleanupReport) {
  if values.length() < 3 {
    return (
      values.copy(),
      { removed: 0, merged: 0, adjusted: 0, maximum_adjustment: 0.0 },
    )
  }
  let result : Array[Double] = [values[0]]
  let mut removed = 0
  let mut merged = 0
  for index in 1.. Array[Double] {
  let result = values.copy()
  if result.length() < 2 {
    return result
  }
  if increasing {
    for index in 1.. Array[Double] {
  let result : Array[Double] = []
  for value in values {
    result.push(clamp(value, minimum, maximum))
  }
  result
}

///|
pub fn resample_constrained_signal(
  values : Array[Double],
  count : Int,
) -> Array[Double] {
  if values.length() == 0 {
    return []
  }
  let result : Array[Double] = []
  let actual = count.max(1)
  for index in 0..