///|
/// 2×3 affine transform matrix.
/// Maps `(x, y)` -> `(a*x + c*y + e, b*x + d*y + f)`.
/// Storage follows the Canvas2D convention `setTransform(a, b, c, d, e, f)`.
pub struct Matrix2D {
  a : Double
  b : Double
  c : Double
  d : Double
  e : Double
  f : Double
} derive(Eq, @debug.Debug)

///|
pub impl Show for Matrix2D with fn output(self, logger) {
  logger.write_string("Matrix2D { a: ")
  logger.write_object(self.a)
  logger.write_string(", b: ")
  logger.write_object(self.b)
  logger.write_string(", c: ")
  logger.write_object(self.c)
  logger.write_string(", d: ")
  logger.write_object(self.d)
  logger.write_string(", e: ")
  logger.write_object(self.e)
  logger.write_string(", f: ")
  logger.write_object(self.f)
  logger.write_string(" }")
}

///|
pub fn Matrix2D::identity() -> Matrix2D {
  { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
}

///|
pub fn Matrix2D::of(
  a : Double,
  b : Double,
  c : Double,
  d : Double,
  e : Double,
  f : Double,
) -> Matrix2D {
  { a, b, c, d, e, f }
}

///|
pub fn Matrix2D::multiply(self : Matrix2D, o : Matrix2D) -> Matrix2D {
  {
    a: self.a * o.a + self.c * o.b,
    b: self.b * o.a + self.d * o.b,
    c: self.a * o.c + self.c * o.d,
    d: self.b * o.c + self.d * o.d,
    e: self.a * o.e + self.c * o.f + self.e,
    f: self.b * o.e + self.d * o.f + self.f,
  }
}

///|
pub fn Matrix2D::translate(self : Matrix2D, x : Double, y : Double) -> Matrix2D {
  self.multiply(Matrix2D::of(1.0, 0.0, 0.0, 1.0, x, y))
}

///|
pub fn Matrix2D::scale(self : Matrix2D, sx : Double, sy : Double) -> Matrix2D {
  self.multiply(Matrix2D::of(sx, 0.0, 0.0, sy, 0.0, 0.0))
}

///|
pub fn Matrix2D::rotate(self : Matrix2D, angle : Double) -> Matrix2D {
  let c = @math.cos(angle)
  let s = @math.sin(angle)
  self.multiply(Matrix2D::of(c, s, -s, c, 0.0, 0.0))
}

///|
pub fn Matrix2D::transform_point(
  self : Matrix2D,
  x : Double,
  y : Double,
) -> (Double, Double) {
  (self.a * x + self.c * y + self.e, self.b * x + self.d * y + self.f)
}

///|
/// Returns the affine inverse of this matrix, or `None` if the matrix is
/// singular (determinant is zero).
pub fn Matrix2D::invert(self : Matrix2D) -> Matrix2D? {
  let det = self.a * self.d - self.b * self.c
  if det == 0.0 {
    None
  } else {
    let inv_det = 1.0 / det
    Some({
      a: self.d * inv_det,
      b: -self.b * inv_det,
      c: -self.c * inv_det,
      d: self.a * inv_det,
      e: (self.c * self.f - self.d * self.e) * inv_det,
      f: (self.b * self.e - self.a * self.f) * inv_det,
    })
  }
}