// Transform composition utilities for interactive editing and interpolation.

///|
pub(all) struct TransformComponents {
  translation : Vec2
  scale : Vec2
  rotation : Double
  skew : Vec2
} derive(Eq, Debug)

///|
pub fn TransformComponents::new(
  translation : Vec2,
  scale : Vec2,
  rotation : Double,
  skew : Vec2,
) -> TransformComponents {
  { translation, scale, rotation, skew }
}

///|
pub fn TransformComponents::identity() -> TransformComponents {
  TransformComponents::new(Vec2::zero(), Vec2::new(1.0, 1.0), 0.0, Vec2::zero())
}

///|
pub fn TransformComponents::to_matrix(self : TransformComponents) -> Matrix2D {
  Matrix2D::translation(self.translation.x, self.translation.y)
  .multiply(Matrix2D::rotation(self.rotation))
  .multiply(Matrix2D::shearing(self.skew.x, self.skew.y))
  .multiply(Matrix2D::scaling(self.scale.x, self.scale.y))
}

///|
pub fn TransformComponents::lerp(
  self : TransformComponents,
  target : TransformComponents,
  amount : Double,
) -> TransformComponents {
  let t = clamp_double(amount, 0.0, 1.0)
  {
    translation: self.translation.lerp(target.translation, t),
    scale: self.scale.lerp(target.scale, t),
    rotation: self.rotation + (target.rotation - self.rotation) * t,
    skew: self.skew.lerp(target.skew, t),
  }
}

///|
pub fn Matrix2D::components(self : Matrix2D) -> TransformComponents {
  let decomposition = self.decompose()
  TransformComponents::new(
    decomposition.translation,
    decomposition.scale,
    decomposition.rotation,
    decomposition.skew,
  )
}

///|
pub fn Matrix2D::lerp(
  self : Matrix2D,
  target : Matrix2D,
  amount : Double,
) -> Matrix2D {
  self.components().lerp(target.components(), amount).to_matrix()
}

///|
pub fn Matrix2D::is_identity(self : Matrix2D, tolerance : Double) -> Bool {
  self.approximately_equal(Matrix2D::identity(), tolerance)
}

///|
pub fn Matrix2D::approximately_equal(
  self : Matrix2D,
  target : Matrix2D,
  tolerance : Double,
) -> Bool {
  nearly_equal(self.a, target.a, tolerance) &&
  nearly_equal(self.b, target.b, tolerance) &&
  nearly_equal(self.c, target.c, tolerance) &&
  nearly_equal(self.d, target.d, tolerance) &&
  nearly_equal(self.tx, target.tx, tolerance) &&
  nearly_equal(self.ty, target.ty, tolerance)
}

///|
pub fn Matrix2D::transform_points(
  self : Matrix2D,
  points : Array[Vec2],
) -> Array[Vec2] {
  let result : Array[Vec2] = []
  for i = 0; i < points.length(); i = i + 1 {
    result.push(self.transform_point(points[i]))
  }
  result
}

///|
pub fn Matrix2D::around(self : Matrix2D, center : Vec2) -> Matrix2D {
  Matrix2D::translation(center.x, center.y)
  .multiply(self)
  .multiply(Matrix2D::translation(-center.x, -center.y))
}

///|
pub fn Matrix2D::with_translation(
  self : Matrix2D,
  translation : Vec2,
) -> Matrix2D {
  { ..self, tx: translation.x, ty: translation.y }
}

///|
pub fn Matrix2D::reflection_x() -> Matrix2D {
  Matrix2D::scaling(-1.0, 1.0)
}

///|
pub fn Matrix2D::reflection_y() -> Matrix2D {
  Matrix2D::scaling(1.0, -1.0)
}

///|
pub fn Matrix2D::uniform_scale(self : Matrix2D) -> Double {
  let x = Vec2::new(self.a, self.b).length()
  let y = Vec2::new(self.c, self.d).length()
  (x + y) * 0.5
}

///|
pub fn Matrix2D::maximum_scale(self : Matrix2D) -> Double {
  let x = Vec2::new(self.a, self.b).length()
  let y = Vec2::new(self.c, self.d).length()
  if x > y {
    x
  } else {
    y
  }
}

///|
pub fn Matrix2D::minimum_scale(self : Matrix2D) -> Double {
  let x = Vec2::new(self.a, self.b).length()
  let y = Vec2::new(self.c, self.d).length()
  if x < y {
    x
  } else {
    y
  }
}

///|
pub fn interpolate_transform(
  start : Matrix2D,
  target : Matrix2D,
  amount : Double,
) -> Matrix2D {
  start.lerp(target, amount)
}