///|
/// Integer affine transforms for controlled sprite placement.
pub(all) struct Affine2i {
  a : Int
  b : Int
  c : Int
  d : Int
  tx : Int
  ty : Int
  scale : Int
} derive(Debug)

///|
pub fn Affine2i::identity() -> Affine2i {
  { a: 1000, b: 0, c: 0, d: 1000, tx: 0, ty: 0, scale: 1000 }
}

///|
pub fn Affine2i::translate(self : Affine2i, x : Int, y : Int) -> Affine2i {
  { ..self, tx: self.tx + x * self.scale, ty: self.ty + y * self.scale }
}

///|
pub fn Affine2i::scale(self : Affine2i, per_mille : Int) -> Affine2i {
  {
    ..self,
    a: self.a * per_mille / 1000,
    b: self.b * per_mille / 1000,
    c: self.c * per_mille / 1000,
    d: self.d * per_mille / 1000,
  }
}

///|
pub fn Affine2i::apply(self : Affine2i, x : Int, y : Int) -> (Int, Int) {
  (
    (self.a * x + self.b * y + self.tx) / self.scale,
    (self.c * x + self.d * y + self.ty) / self.scale,
  )
}

///|
pub fn Affine2i::inverse_apply(self : Affine2i, x : Int, y : Int) -> (Int, Int) {
  let determinant = self.a * self.d - self.b * self.c
  if determinant == 0 {
    (x, y)
  } else {
    let px = x * self.scale - self.tx
    let py = y * self.scale - self.ty
    (
      (self.d * px - self.b * py) / determinant,
      (-self.c * px + self.a * py) / determinant,
    )
  }
}

///|
/// Apply an affine transform with nearest-neighbor sampling and transparent fill.
pub fn Canvas::affine(self : Canvas, transform : Affine2i) -> Canvas {
  let result = Canvas::new(
    width=self.width,
    height=self.height,
    fill=rgba(0, 0, 0, 0),
  )
  for y in 0.. ()
        Some(color) => result.pixels[x + y * self.width] = color
      }
    }
  }
  result
}

///|
/// Rotate around the canvas center using a right-angle approximation.
pub fn Canvas::affine_rotate(
  self : Canvas,
  rotation : CanvasRotation,
) -> Canvas {
  match rotation {
    Clockwise90 => {
      let result = self.rotate90()
      result
    }
    HalfTurn => self.rotate180()
    Clockwise270 => self.rotate270()
  }
}

///|
/// Place a transformed canvas at a destination with clipping.
pub fn Canvas::draw_transformed(
  self : Canvas,
  source : Canvas,
  transform : Affine2i,
) -> Int {
  let transformed = source.affine(transform)
  self.blit(transformed, destination_x=0, destination_y=0)
}

///|
/// Fit a source into an output rectangle using a center translation.
pub fn Canvas::fit_affine(self : Canvas, width : Int, height : Int) -> Canvas {
  if self.width == 0 || self.height == 0 {
    Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
  } else {
    let horizontal = width * 1000 / self.width
    let vertical = height * 1000 / self.height
    let scale = if horizontal < vertical { horizontal } else { vertical }
    let scaled = self.resize_nearest(
      width=self.width * scale / 1000,
      height=self.height * scale / 1000,
    )
    let result = Canvas::new(width~, height~, fill=rgba(0, 0, 0, 0))
    ignore(
      result.blit(
        scaled,
        destination_x=(width - scaled.width) / 2,
        destination_y=(height - scaled.height) / 2,
      ),
    )
    result
  }
}