#![allow(dead_code)]

use std::ops::{Add, Mul};

use crate::point::Point;
use crate::sealed::Sealed;
use crate::wide4::Float4;

/// A forward affine transform.
///
/// Use this for working with transforms that still need to be
/// manipulated or composed with other transforms, or for storing
/// transforms more compactly.
///
/// Note: slightly counter-intuitively, even though this can perform
/// forward (but not inverse) transforms on points and vectors, it is
/// capable of *inverse* (but not forward) transforms on surface normals.
/// This is because forward transforms on surface normals require the
/// inverse transform matrix.
///
/// Convert to an [`XformFull`] for a larger-format type capable of
/// efficiently performing both forward and inverse transforms on all
/// types, but which is effectively "frozen" in terms of further
/// manipulation of the transform itself.
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(C)]
pub struct Xform {
    /// Rotation/scale/shear matrix.
    pub m: [Float4; 3],

    /// Translation.
    pub t: Float4,
}

impl Xform {
    /// Creates a new affine transform with the specified values:
    ///
    /// ```text
    /// a d g j
    /// b e h k
    /// c f i l
    /// ```
    ///
    /// Where j, k, and l are the xyz translation component.
    #[inline]
    #[allow(clippy::many_single_char_names)]
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        a: f32,
        b: f32,
        c: f32,
        d: f32,
        e: f32,
        f: f32,
        g: f32,
        h: f32,
        i: f32,
        j: f32,
        k: f32,
        l: f32,
    ) -> Self {
        Self {
            m: [
                Float4::new(a, b, c, 0.0),
                Float4::new(d, e, f, 0.0),
                Float4::new(g, h, i, 0.0),
            ],
            t: Float4::new(j, k, l, 0.0),
        }
    }

    /// Creates a new identity transform.
    #[inline]
    pub fn identity() -> Self {
        Self {
            m: [
                Float4::new(1.0, 0.0, 0.0, 0.0),
                Float4::new(0.0, 1.0, 0.0, 0.0),
                Float4::new(0.0, 0.0, 1.0, 0.0),
            ],
            t: Float4::splat(0.0),
        }
    }

    #[inline]
    pub fn from_location(loc: Point) -> Xform {
        Self {
            m: [
                Float4::new(1.0, 0.0, 0.0, 0.0),
                Float4::new(0.0, 1.0, 0.0, 0.0),
                Float4::new(0.0, 0.0, 1.0, 0.0),
            ],
            t: loc.0,
        }
    }

    /// Returns whether the matrices are approximately equal to each other.
    /// Each corresponding element in the matrices cannot have a relative
    /// error exceeding epsilon.
    pub(crate) fn aprx_eq(&self, other: Xform, max_ulps: u32) -> bool {
        let mut eq = true;
        eq &= Float4::aprx_eq(self.m[0], other.m[0], max_ulps);
        eq &= Float4::aprx_eq(self.m[1], other.m[1], max_ulps);
        eq &= Float4::aprx_eq(self.m[2], other.m[2], max_ulps);
        eq &= Float4::aprx_eq(self.t, other.t, max_ulps);
        eq
    }

    /// Compute the "full" version of the transform.
    #[inline]
    pub fn to_full(&self) -> Option<XformFull> {
        if let Some(inv_m) = Float4::invert_3x3(&self.m) {
            Some(XformFull {
                fwd: *self,
                inv_m: inv_m,
            })
        } else {
            None
        }
    }

    /// Faster but less precise version of `to_full()`.
    #[inline]
    pub fn to_full_fast(&self) -> Option<XformFull> {
        if let Some(inv_m) = Float4::invert_3x3_fast(&self.m) {
            Some(XformFull {
                fwd: *self,
                inv_m: inv_m,
            })
        } else {
            None
        }
    }

    /// Composes two transforms together.
    ///
    /// The resulting transform is the same as doing `self` and then
    /// `rhs` in sequence.
    #[inline]
    pub fn compose(&self, rhs: &Self) -> Self {
        let (m, t) = Float4::affine_mul_affine(&self.m, self.t, &rhs.m, rhs.t);
        Self { m: m, t: t }
    }

    /// Composes two transforms together.
    ///
    /// Faster but less precise version.
    #[inline]
    pub fn compose_fast(&self, rhs: &Self) -> Self {
        let (m, t) = Float4::affine_mul_affine_fast(&self.m, self.t, &rhs.m, rhs.t);
        Self { m: m, t: t }
    }
}

impl Default for Xform {
    fn default() -> Self {
        Self::identity()
    }
}

/// Multiply a matrix by a f32
impl Mul<f32> for Xform {
    type Output = Self;

    #[inline]
    fn mul(self, rhs: f32) -> Self {
        Self {
            m: [self.m[0] * rhs, self.m[1] * rhs, self.m[2] * rhs],
            t: self.t * rhs,
        }
    }
}

/// Add two matrices together
impl Add for Xform {
    type Output = Self;

    #[inline]
    fn add(self, rhs: Self) -> Self {
        Self {
            m: [
                self.m[0] + rhs.m[0],
                self.m[1] + rhs.m[1],
                self.m[2] + rhs.m[2],
            ],
            t: self.t + rhs.t,
        }
    }
}

impl AsXform for Xform {
    #[inline(always)]
    fn as_xform(&self) -> &Xform {
        self
    }
}

impl Sealed for Xform {}

//-------------------------------------------------------------

/// A combined forward/inverse affine transform.
///
/// Unlike [`Xform`], this can perform both forward and inverse
/// transforms on all types.  However, it also takes up more space and
/// is effectively "frozen" in terms of further manipulation.  Prefer
/// [`Xform`] when manipulating or composing transforms, and also
/// when storing transforms if space is a consideration.
///
/// Note: only the 3x3 part of the transform is stored inverted.  This
/// is because it's both trivial and more numerically stable to reuse
/// the forward translation vector to do inverse transforms, as
/// `(point - fwd.t) * inv_m`.
#[derive(Debug, Copy, Clone)]
#[repr(C)]
pub struct XformFull {
    /// Forward transform.
    pub fwd: Xform,

    /// Inverse rotation/scale/shear matrix.
    pub inv_m: [Float4; 3],
}

impl XformFull {
    pub fn identity() -> Self {
        Self {
            fwd: Xform {
                m: [
                    Float4::new(1.0, 0.0, 0.0, 0.0),
                    Float4::new(0.0, 1.0, 0.0, 0.0),
                    Float4::new(0.0, 0.0, 1.0, 0.0),
                ],
                t: Float4::splat(0.0),
            },
            inv_m: [
                Float4::new(1.0, 0.0, 0.0, 0.0),
                Float4::new(0.0, 1.0, 0.0, 0.0),
                Float4::new(0.0, 0.0, 1.0, 0.0),
            ],
        }
    }
}

impl AsXform for XformFull {
    #[inline(always)]
    fn as_xform(&self) -> &Xform {
        &self.fwd
    }
}

impl Sealed for XformFull {}

//-------------------------------------------------------------

pub trait AsXform: Sealed {
    fn as_xform(&self) -> &Xform;
}

//-------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn equality() {
        let a = Xform::identity();
        let b = Xform::identity();
        let c = Xform::new(1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0, 0.0, 0.0, 0.0, 1.1, 0.0);

        assert_eq!(a, b);
        assert!(a != c);
    }

    #[test]
    fn approximate_equality() {
        let a = Xform::identity();
        let b = Xform::new(
            1.000001, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0, 1.000001, 0.0, 0.0, 0.0,
        );
        let c = Xform::new(
            1.000003, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0, 1.000003, 0.0, 0.0, 0.0,
        );

        assert!(a.aprx_eq(b, 10));
        assert!(!a.aprx_eq(b, 6));

        assert!(a.aprx_eq(c, 27));
        assert!(!a.aprx_eq(c, 23));
    }

    #[test]
    fn compose() {
        let a = Xform::new(1.0, 3.0, 9.0, 2.0, 6.0, 2.0, 2.0, 7.0, 11.0, 1.5, 8.0, 12.0);
        let b = Xform::new(
            1.0, 2.0, 3.0, 5.0, 6.0, 7.0, 9.0, 10.0, 11.0, 13.0, 14.0, 15.0,
        );
        let c = Xform::new(
            97.0, 110.0, 123.0, 50.0, 60.0, 70.0, 136.0, 156.0, 176.0, 162.5, 185.0, 207.5,
        );

        assert_eq!(a.compose(&b), c);
        assert_eq!(a.compose_fast(&b), c);
    }
}
