///|
/// A pure affine transformation in Cairo component order.
///
/// Points are transformed as `x' = xx*x + xy*y + x0` and
/// `y' = yx*x + yy*y + y0`. All transformation methods return new values;
/// they never mutate the receiver as pycairo's corresponding methods do.
pub struct Matrix {
  xx : Double
  yx : Double
  xy : Double
  yy : Double
  x0 : Double
  y0 : Double
} derive(Eq, Debug)

///|
/// Construct a matrix from `(xx, yx, xy, yy, x0, y0)` components.
///
/// Omitted components produce the identity matrix.
pub fn Matrix::new(
  xx? : Double = 1.0,
  yx? : Double = 0.0,
  xy? : Double = 0.0,
  yy? : Double = 1.0,
  x0? : Double = 0.0,
  y0? : Double = 0.0,
) -> Matrix {
  { xx, yx, xy, yy, x0, y0 }
}

///|
/// Construct a rotation by `radians` around the origin.
///
/// Positive angles rotate from the positive X axis toward positive Y, which
/// appears clockwise with Cairo's default downward-pointing Y axis.
pub fn Matrix::init_rotate(radians : Double) -> Matrix {
  let s = @math.sin(radians)
  let c = @math.cos(radians)
  { xx: c, yx: s, xy: -s, yy: c, x0: 0.0, y0: 0.0 }
}

///|
/// Return `(xx, yx, xy, yy, x0, y0)`.
pub fn Matrix::components(
  self : Matrix,
) -> (Double, Double, Double, Double, Double, Double) {
  (self.xx, self.yx, self.xy, self.yy, self.x0, self.y0)
}

///|
/// Return the component at index `0..5` in Cairo/pycairo order.
///
/// Raises `CairoInvalidArgument(InvalidIndex, _)` for any other index.
pub fn Matrix::component(self : Matrix, index : Int) -> Double raise CairoError {
  match index {
    0 => self.xx
    1 => self.yx
    2 => self.xy
    3 => self.yy
    4 => self.x0
    5 => self.y0
    _ => raise CairoInvalidArgument(InvalidIndex, InvalidIndex.message())
  }
}

///|
/// Index the `(xx, yx, xy, yy, x0, y0)` component sequence.
///
/// Raises `CairoInvalidArgument(InvalidIndex, _)` outside `0..5`.
#alias("_[_]")
pub fn Matrix::at(self : Matrix, index : Int) -> Double raise CairoError {
  self.component(index)
}

///|
/// Return a transform that first translates by `(tx, ty)`, then applies `self`.
///
/// The receiver is unchanged.
pub fn Matrix::translate(self : Matrix, tx : Double, ty : Double) -> Matrix {
  {
    xx: self.xx,
    yx: self.yx,
    xy: self.xy,
    yy: self.yy,
    x0: self.x0 + tx * self.xx + ty * self.xy,
    y0: self.y0 + tx * self.yx + ty * self.yy,
  }
}

///|
/// Return a transform that first rotates by `radians`, then applies `self`.
///
/// The receiver is unchanged.
pub fn Matrix::rotate(self : Matrix, radians : Double) -> Matrix {
  Matrix::init_rotate(radians).multiply(self)
}

///|
/// Return a transform that first scales by `(sx, sy)`, then applies `self`.
///
/// The receiver is unchanged.
pub fn Matrix::scale(self : Matrix, sx : Double, sy : Double) -> Matrix {
  {
    xx: self.xx * sx,
    yx: self.yx * sx,
    xy: self.xy * sy,
    yy: self.yy * sy,
    x0: self.x0,
    y0: self.y0,
  }
}

///|
/// Return the inverse affine transformation without changing `self`.
///
/// Raises `CairoInvalidArgument(InvalidMatrix, _)` when the matrix is
/// degenerate and has no inverse.
pub fn Matrix::invert(self : Matrix) -> Matrix raise CairoError {
  let det = self.xx * self.yy - self.yx * self.xy
  if det == 0.0 {
    check_status(InvalidMatrix)
  }
  {
    xx: self.yy / det,
    yx: -self.yx / det,
    xy: -self.xy / det,
    yy: self.xx / det,
    x0: (self.xy * self.y0 - self.yy * self.x0) / det,
    y0: (self.yx * self.x0 - self.xx * self.y0) / det,
  }
}

///|
/// Compose `self` with `other`, applying `self` to coordinates first.
///
/// The `*` operator delegates to this method. Neither operand is mutated.
pub fn Matrix::multiply(self : Matrix, other : Matrix) -> Matrix {
  {
    xx: self.xx * other.xx + self.yx * other.xy,
    yx: self.xx * other.yx + self.yx * other.yy,
    xy: self.xy * other.xx + self.yy * other.xy,
    yy: self.xy * other.yx + self.yy * other.yy,
    x0: self.x0 * other.xx + self.y0 * other.xy + other.x0,
    y0: self.x0 * other.yx + self.y0 * other.yy + other.y0,
  }
}

///|
pub impl Mul for Matrix with fn mul(self, other) {
  self.multiply(other)
}

///|
/// Transform a point, including the matrix's translation components.
pub fn Matrix::transform_point(
  self : Matrix,
  x : Double,
  y : Double,
) -> (Double, Double) {
  (self.xx * x + self.xy * y + self.x0, self.yx * x + self.yy * y + self.y0)
}

///|
/// Transform a distance vector while ignoring translation.
pub fn Matrix::transform_distance(
  self : Matrix,
  dx : Double,
  dy : Double,
) -> (Double, Double) {
  (self.xx * dx + self.xy * dy, self.yx * dx + self.yy * dy)
}