///|
/// Matrix decomposition for interactive editors and animation systems.
pub(all) struct MatrixDecomposition {
  translation : Vec2
  rotation : Double
  scale : Vec2
  determinant : Double
} derive(Eq, Debug, ToJson)

///|
pub fn Matrix2D::determinant(self : Matrix2D) -> Double {
  self.a * self.d - self.b * self.c
}

///|
pub fn Matrix2D::is_identity(
  self : Matrix2D,
  epsilon? : Double = 0.000000001,
) -> Bool {
  (self.a - 1.0).abs() <= epsilon &&
  self.b.abs() <= epsilon &&
  self.c.abs() <= epsilon &&
  (self.d - 1.0).abs() <= epsilon &&
  self.tx.abs() <= epsilon &&
  self.ty.abs() <= epsilon
}

///|
pub fn Matrix2D::inverse(self : Matrix2D) -> Matrix2D? {
  let det = self.determinant()
  if det.abs() <= 0.000000000001 {
    None
  } else {
    let inverse_det = 1.0 / det
    Some({
      a: self.d * inverse_det,
      b: -self.b * inverse_det,
      c: -self.c * inverse_det,
      d: self.a * inverse_det,
      tx: (self.c * self.ty - self.d * self.tx) * inverse_det,
      ty: (self.b * self.tx - self.a * self.ty) * inverse_det,
    })
  }
}

///|
pub fn Matrix2D::decompose(self : Matrix2D) -> MatrixDecomposition {
  let scale_x = Vec2::new(self.a, self.b).length()
  let determinant = self.determinant()
  let scale_y = if scale_x == 0.0 { 0.0 } else { determinant / scale_x }
  {
    translation: Vec2::new(self.tx, self.ty),
    rotation: @math.atan2(self.b, self.a),
    scale: Vec2::new(scale_x, scale_y),
    determinant,
  }
}

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

///|
pub fn Matrix2D::map_rect(self : Matrix2D, rect : Rect) -> Rect {
  let corners = [
    Vec2::new(rect.x, rect.y),
    Vec2::new(rect.right(), rect.y),
    Vec2::new(rect.right(), rect.bottom()),
    Vec2::new(rect.x, rect.bottom()),
  ]
  let transformed = []
  for corner in corners {
    let (x, y) = self.transform_point(corner.x, corner.y)
    transformed.push(Vec2::new(x, y))
  }
  bounds_of_points(transformed)
}

///|
pub fn interpolate_matrix(
  start : Matrix2D,
  end : Matrix2D,
  amount : Double,
) -> Matrix2D {
  let t = if amount < 0.0 { 0.0 } else if amount > 1.0 { 1.0 } else { amount }
  {
    a: start.a + (end.a - start.a) * t,
    b: start.b + (end.b - start.b) * t,
    c: start.c + (end.c - start.c) * t,
    d: start.d + (end.d - start.d) * t,
    tx: start.tx + (end.tx - start.tx) * t,
    ty: start.ty + (end.ty - start.ty) * t,
  }
}