// Cubic Bezier easing with control points

///|
pub struct CubicBezier {
  x1 : Double
  y1 : Double
  x2 : Double
  y2 : Double
}

// Create a cubic Bezier easing function

///|
pub fn CubicBezier::new(
  x1 : Double,
  y1 : Double,
  x2 : Double,
  y2 : Double,
) -> CubicBezier {
  { x1, y1, x2, y2 }
}

// Evaluate a custom cubic Bezier motion curve at time t.

///|
pub fn CubicBezier::value_at(self : CubicBezier, t : Double) -> Double {
  if t <= 0.0 {
    return 0.0
  }
  if t >= 1.0 {
    return 1.0
  }

  // Find x that corresponds to t using Newton-Raphson
  let x = solve_bezier_x(t, self.x1, self.x2)

  // Calculate y at that x
  bezier_y(x, self.y1, self.y2)
}

///|
fn solve_bezier_x(t : Double, x1 : Double, x2 : Double) -> Double {
  let mut x = t
  let epsilon = 0.0001

  for _ in 0..<10 {
    let current_t = bezier_x(x, x1, x2)
    let diff = current_t - t
    if diff.abs() < epsilon {
      return x
    }
    let derivative = bezier_x_derivative(x, x1, x2)
    if derivative.abs() < epsilon {
      break
    }
    x = x - diff / derivative
  }

  x
}

///|
fn bezier_x(t : Double, x1 : Double, x2 : Double) -> Double {
  let t2 = t * t
  let t3 = t2 * t
  let mt = 1.0 - t
  let mt2 = mt * mt
  3.0 * mt2 * t * x1 + 3.0 * mt * t2 * x2 + t3
}

///|
fn bezier_x_derivative(t : Double, x1 : Double, x2 : Double) -> Double {
  let t2 = t * t
  let mt = 1.0 - t
  let mt2 = mt * mt
  3.0 * mt2 * x1 + 6.0 * mt * t * (x2 - x1) + 3.0 * t2 * (1.0 - x2)
}

///|
fn bezier_y(t : Double, y1 : Double, y2 : Double) -> Double {
  let t2 = t * t
  let t3 = t2 * t
  let mt = 1.0 - t
  let mt2 = mt * mt
  3.0 * mt2 * t * y1 + 3.0 * mt * t2 * y2 + t3
}