// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// A 2D affine transform: a linear map and a translation, without the row of a
/// matrix that does nothing but hold `(0, 0, 1)`.
///
/// Composition, inversion and applying are all cheaper than the 3×3 matrix that
/// would carry the same thing, and there is no way to build one of these that
/// is not affine — which is what UI layout, sprite placement and 2D physics
/// want.
///
/// Values are immutable.
#derive(Eq)
pub(all) struct Affine2 {
  /// The part that turns and scales: a 2×2 matrix.
  linear : Mat2
  /// The part that moves.
  translation : Vec2
}

///|
pub extend Affine2 with Eq::{equal, not_equal}

///|
/// The transform that maps `v` to `linear * v + translation`.
pub fn Affine2::new(linear : Mat2, translation : Vec2) -> Affine2 {
  { linear, translation, }
}

///|
/// The transform that changes nothing.
pub fn Affine2::identity() -> Affine2 {
  { linear: Mat2::identity(), translation: Vec2::zero(), }
}

///|
/// The transform that moves every point by `offset`.
pub fn Affine2::from_translation(offset : Vec2) -> Affine2 {
  { linear: Mat2::identity(), translation: offset, }
}

///|
/// The transform that scales each axis by the matching component of `scale`.
pub fn Affine2::from_scale(scale : Vec2) -> Affine2 {
  { linear: Mat2::from_scale(scale), translation: Vec2::zero(), }
}

///|
/// The transform that turns counter-clockwise by `radians` about the origin.
pub fn Affine2::from_angle(radians : Float) -> Affine2 {
  { linear: Mat2::from_angle(radians), translation: Vec2::zero(), }
}

///|
/// The transform that turns by `radians` and then moves by `offset`.
pub fn Affine2::from_angle_translation(
  radians : Float,
  offset : Vec2,
) -> Affine2 {
  { linear: Mat2::from_angle(radians), translation: offset, }
}

///|
/// The same transform as a 3×3 matrix, with `(0, 0, 1)` as its last row.
pub fn Affine2::to_mat3(self : Affine2) -> Mat3 {
  {
    c0: { x: self.linear.c0.x, y: self.linear.c0.y, z: 0.0, },
    c1: { x: self.linear.c1.x, y: self.linear.c1.y, z: 0.0, },
    c2: { x: self.translation.x, y: self.translation.y, z: 1.0, },
  }
}

///|
/// The affine transform a 3×3 matrix holds, or `None` when the matrix is not
/// affine.
///
/// A matrix whose last row is not `(0, 0, 1)` — a projection, say — carries a
/// perspective that this type cannot hold, and there is no transform to return.
pub fn Affine2::from_mat3(m : Mat3) -> Affine2? {
  if m.get(2, 0) != 0.0 || m.get(2, 1) != 0.0 || m.get(2, 2) != 1.0 {
    None
  } else {
    Some({
      linear: Mat2::from_cols(m.c0.xy(), m.c1.xy()),
      translation: { x: m.c2.x, y: m.c2.y, },
    })
  }
}

///|
/// The transform that undoes this one, or `None` when the linear part is
/// singular.
///
/// A singular linear part flattens the plane onto a line or a point, and no
/// transform brings that back.
pub fn Affine2::inverse(self : Affine2) -> Affine2? {
  match self.linear.inverse() {
    None => None
    Some(inverse) =>
      Some({
        linear: inverse,
        translation: -inverse.mul_vec2(self.translation),
      })
  }
}

///|
/// The point the transform takes `p` to.
pub fn Affine2::transform_point(self : Affine2, p : Vec2) -> Vec2 {
  self.linear.mul_vec2(p) + self.translation
}

///|
/// The direction the transform takes `v` to, without the translation.
///
/// A direction has no position, so moving the origin must leave it alone.
pub fn Affine2::transform_vector(self : Affine2, v : Vec2) -> Vec2 {
  self.linear.mul_vec2(v)
}

///|
/// Whether the two transforms agree to within `eps` element by element.
pub fn Affine2::approx(self : Affine2, other : Affine2, eps? : Float) -> Bool {
  self.linear.approx(other.linear, eps?) &&
  self.translation.approx(other.translation, eps?)
}

///|
/// Whether every element is finite.
pub fn Affine2::is_finite(self : Affine2) -> Bool {
  self.linear.is_finite() && self.translation.is_finite()
}

///|
/// The transform that applies `other` first and then `self`.
pub impl Mul for Affine2 with fn mul(self, other) {
  {
    linear: self.linear * other.linear,
    translation: self.linear.mul_vec2(other.translation) + self.translation,
  }
}

///|
pub extend Affine2 with Mul::{mul}

///|
/// The composition with the inverse of the right-hand side; aborts when that
/// side is singular.
pub impl Div for Affine2 with fn div(self, other) {
  match other.inverse() {
    Some(inverse) => self * inverse
    None => abort("Affine2 division by a singular transform")
  }
}

///|
pub extend Affine2 with Div::{div}

///|
pub impl Show for Affine2 with fn output(self, logger) {
  logger.write_string("Affine2[\{self.linear}, \{self.translation}]")
}

///|
pub extend Affine2 with Show::{output, to_string}

///|
pub impl Debug for Affine2 with fn to_repr(self) {
  @debug.Repr::ctor("Affine2", [
    (Some("linear"), self.linear.to_repr()),
    (Some("translation"), self.translation.to_repr()),
  ])
}

///|
pub extend Affine2 with Debug::{to_repr}

///|
/// A 3D affine transform: a linear map and a translation, without the fourth
/// row of a matrix that does nothing but hold `(0, 0, 0, 1)`.
///
/// This is the transform a scene node carries — position, rotation and scale
/// applied to its geometry — and it is cheaper than the 4×4 matrix that would
/// also happen to work, since the wasted row would be multiplied four times per
/// vector.
///
/// Values are immutable.
#derive(Eq)
pub(all) struct Affine3 {
  /// The part that turns and scales: a 3×3 matrix.
  linear : Mat3
  /// The part that moves.
  translation : Vec3
}

///|
pub extend Affine3 with Eq::{equal, not_equal}

///|
/// The transform that maps `v` to `linear * v + translation`.
pub fn Affine3::new(linear : Mat3, translation : Vec3) -> Affine3 {
  { linear, translation, }
}

///|
/// The transform that changes nothing.
pub fn Affine3::identity() -> Affine3 {
  { linear: Mat3::identity(), translation: Vec3::zero(), }
}

///|
/// The transform that moves every point by `offset`.
pub fn Affine3::from_translation(offset : Vec3) -> Affine3 {
  { linear: Mat3::identity(), translation: offset, }
}

///|
/// The transform that scales each axis by the matching component of `scale`.
pub fn Affine3::from_scale(scale : Vec3) -> Affine3 {
  { linear: Mat3::from_scale(scale), translation: Vec3::zero(), }
}

///|
/// The transform that applies the rotation, about the origin.
pub fn Affine3::from_rotation(rotation : Quat) -> Affine3 {
  { linear: rotation.to_mat3(), translation: Vec3::zero(), }
}

///|
/// The transform that turns by `radians` about `axis`, by the right-hand rule.
pub fn Affine3::from_axis_angle(axis : Vec3, radians : Float) -> Affine3 {
  { linear: Mat3::from_axis_angle(axis, radians), translation: Vec3::zero(), }
}

///|
/// The transform that scales, then rotates, then moves: the three parts of a
/// `Trs`.
pub fn Affine3::from_trs(trs : Trs) -> Affine3 {
  {
    linear: Mat3::from_mat4(Mat4::from_trs(trs)),
    translation: trs.translation,
  }
}

///|
/// The same transform as a 4×4 matrix, with `(0, 0, 0, 1)` as its last row.
pub fn Affine3::to_mat4(self : Affine3) -> Mat4 {
  {
    c0: self.linear.c0.to_vec4(0.0),
    c1: self.linear.c1.to_vec4(0.0),
    c2: self.linear.c2.to_vec4(0.0),
    c3: self.translation.to_vec4(1.0),
  }
}

///|
/// The affine transform a 4×4 matrix holds, or `None` when the matrix is not
/// affine.
pub fn Affine3::from_mat4(m : Mat4) -> Affine3? {
  if m.get(3, 0) != 0.0 ||
    m.get(3, 1) != 0.0 ||
    m.get(3, 2) != 0.0 ||
    m.get(3, 3) != 1.0 {
    None
  } else {
    Some({ linear: Mat3::from_mat4(m), translation: m.c3.xyz(), })
  }
}

///|
/// The transform that undoes this one, or `None` when the linear part is
/// singular.
pub fn Affine3::inverse(self : Affine3) -> Affine3? {
  match self.linear.inverse() {
    None => None
    Some(inverse) =>
      Some({
        linear: inverse,
        translation: -inverse.mul_vec3(self.translation),
      })
  }
}

///|
/// The point the transform takes `p` to.
pub fn Affine3::transform_point(self : Affine3, p : Vec3) -> Vec3 {
  self.linear.mul_vec3(p) + self.translation
}

///|
/// The direction the transform takes `v` to, without the translation.
pub fn Affine3::transform_vector(self : Affine3, v : Vec3) -> Vec3 {
  self.linear.mul_vec3(v)
}

///|
/// The three parts the transform was built from, or `None` when its linear part
/// is singular or has shear in it. See `Mat4::to_trs`.
pub fn Affine3::to_trs(self : Affine3) -> Trs? {
  self.to_mat4().to_trs()
}

///|
/// Whether the two transforms agree to within `eps` element by element.
pub fn Affine3::approx(self : Affine3, other : Affine3, eps? : Float) -> Bool {
  self.linear.approx(other.linear, eps?) &&
  self.translation.approx(other.translation, eps?)
}

///|
/// Whether every element is finite.
pub fn Affine3::is_finite(self : Affine3) -> Bool {
  self.linear.is_finite() && self.translation.is_finite()
}

///|
/// The transform that applies `other` first and then `self`.
pub impl Mul for Affine3 with fn mul(self, other) {
  {
    linear: self.linear * other.linear,
    translation: self.linear.mul_vec3(other.translation) + self.translation,
  }
}

///|
pub extend Affine3 with Mul::{mul}

///|
/// The composition with the inverse of the right-hand side; aborts when that
/// side is singular.
pub impl Div for Affine3 with fn div(self, other) {
  match other.inverse() {
    Some(inverse) => self * inverse
    None => abort("Affine3 division by a singular transform")
  }
}

///|
pub extend Affine3 with Div::{div}

///|
pub impl Show for Affine3 with fn output(self, logger) {
  logger.write_string("Affine3[\{self.linear}, \{self.translation}]")
}

///|
pub extend Affine3 with Show::{output, to_string}

///|
pub impl Debug for Affine3 with fn to_repr(self) {
  @debug.Repr::ctor("Affine3", [
    (Some("linear"), self.linear.to_repr()),
    (Some("translation"), self.translation.to_repr()),
  ])
}

///|
pub extend Affine3 with Debug::{to_repr}