//! Native quaternion and 4x4 matrix geometry compatible with `LibreMetaverse`.

#![allow(clippy::cast_possible_truncation)] // C# geometry explicitly narrows transcendental results.
#![allow(clippy::float_cmp)] // Exact zero and component comparisons are reference behavior.
#![allow(clippy::inherent_to_string)] // The mapped C# methods are named ToString.
#![allow(clippy::many_single_char_names)] // Geometry formulas conventionally use compact operands.
#![allow(clippy::missing_errors_doc)] // Result shapes are fixed by the public compatibility map.
#![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped C# surface.
#![allow(clippy::needless_pass_by_value)] // Owned arguments mirror mapped C# value parameters.
#![allow(clippy::should_implement_trait)] // Operator entry points have fixed generated names.
#![allow(clippy::single_match_else)] // TryParse mirrors the reference success/failure branches.
#![allow(clippy::too_many_arguments)] // The 16-scalar constructor is the mapped public API.

use crate::byte_order::{read_single_little_endian, write_single_little_endian};
use crate::compat::Object;
use crate::math_compat::{clamp_f32, format_f32, hash_f32, parse_f32};
use crate::{Error, Vector3, Vector4};

fn sqrt_single(value: f32) -> f32 {
    f64::from(value).sqrt() as f32
}
fn sin_single(value: f32) -> f32 {
    f64::from(value).sin() as f32
}
fn cos_single(value: f32) -> f32 {
    f64::from(value).cos() as f32
}
fn acos_single(value: f32) -> f32 {
    f64::from(value).acos() as f32
}
fn atan2_single(y: f32, x: f32) -> f32 {
    f64::from(y).atan2(f64::from(x)) as f32
}

/// A quaternion stored as `(x, y, z, w)`.
///
/// Products use the same Hamilton-product direction as the C# reference.
/// Serialized quaternions contain normalized `x`, `y`, and `z` in explicit
/// little-endian order; the non-negative `w` component is reconstructed.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Quaternion {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub w: f32,
}

impl Quaternion {
    #[must_use]
    pub const fn identity() -> Self {
        Self {
            x: 0.0,
            y: 0.0,
            z: 0.0,
            w: 1.0,
        }
    }

    pub const fn new_with_single_single_single_single(
        x: f32,
        y: f32,
        z: f32,
        w: f32,
    ) -> Result<Self, Error> {
        Ok(Self { x, y, z, w })
    }
    pub const fn new_with_vector3_single(
        vector_part: Vector3,
        scalar_part: f32,
    ) -> Result<Self, Error> {
        Ok(Self {
            x: vector_part.x,
            y: vector_part.y,
            z: vector_part.z,
            w: scalar_part,
        })
    }
    pub fn new_with_single_single_single(x: f32, y: f32, z: f32) -> Result<Self, Error> {
        let xyz_sum = 1.0 - x * x - y * y - z * z;
        Ok(Self {
            x,
            y,
            z,
            w: if xyz_sum > 0.0 {
                sqrt_single(xyz_sum)
            } else {
                0.0
            },
        })
    }
    pub fn new_with_bytes_int32_boolean(
        byte_array: Vec<u8>,
        pos: i32,
        normalized: bool,
    ) -> Result<Self, Error> {
        Self::from_bytes(byte_array, pos, normalized)
    }
    pub fn from_bytes(byte_array: Vec<u8>, pos: i32, normalized: bool) -> Result<Self, Error> {
        let x = read_single_little_endian(&byte_array, pos)?;
        let y = read_single_little_endian(
            &byte_array,
            pos.checked_add(4).ok_or(Error::IndexOutOfRange)?,
        )?;
        let z = read_single_little_endian(
            &byte_array,
            pos.checked_add(8).ok_or(Error::IndexOutOfRange)?,
        )?;
        if normalized {
            Self::new_with_single_single_single(x, y, z)
        } else {
            let w = read_single_little_endian(
                &byte_array,
                pos.checked_add(12).ok_or(Error::IndexOutOfRange)?,
            )?;
            Ok(Self { x, y, z, w })
        }
    }

    fn length_squared_value(self) -> f32 {
        self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w
    }
    fn length_value(self) -> f32 {
        sqrt_single(self.length_squared_value())
    }
    pub fn length(&mut self) -> Result<f32, Error> {
        Ok(self.length_value())
    }
    pub fn length_squared(&mut self) -> Result<f32, Error> {
        Ok(self.length_squared_value())
    }
    pub fn approx_equals(&mut self, quat: Self, tolerance: f32) -> Result<bool, Error> {
        Ok(Self::sub_value(*self, quat).length_squared_value() <= tolerance * tolerance)
    }
    pub fn get_bytes(&self) -> Result<Vec<u8>, Error> {
        let mut bytes = vec![0; 12];
        self.to_bytes(&mut bytes, 0)?;
        Ok(bytes)
    }
    pub fn to_bytes(&self, dest: &mut [u8], pos: i32) -> Result<(), Error> {
        let mut norm = self.length_value();
        if norm == 0.0 {
            return Err(Error::InvalidOperation);
        }
        norm = 1.0 / norm;
        let (x, y, z) = if self.w >= 0.0 {
            (self.x, self.y, self.z)
        } else {
            (-self.x, -self.y, -self.z)
        };
        write_single_little_endian(dest, pos, norm * x)?;
        write_single_little_endian(
            dest,
            pos.checked_add(4).ok_or(Error::IndexOutOfRange)?,
            norm * y,
        )?;
        write_single_little_endian(
            dest,
            pos.checked_add(8).ok_or(Error::IndexOutOfRange)?,
            norm * z,
        )
    }
    pub fn get_euler_angles(
        &self,
        roll: &mut f32,
        pitch: &mut f32,
        yaw: &mut f32,
    ) -> Result<(), Error> {
        *roll = 0.0;
        *pitch = 0.0;
        *yaw = 0.0;
        let squared = Self {
            x: self.x * self.x,
            y: self.y * self.y,
            z: self.z * self.z,
            w: self.w * self.w,
        };
        let m = squared.x + squared.y + squared.z + squared.w;
        if f64::from(m).abs() < 0.001 {
            return Ok(());
        }
        let n = 2.0 * (self.y * self.w + self.x * self.z);
        let p = m * m - n * n;
        if p > 0.0 {
            *roll = atan2_single(
                2.0 * (self.x * self.w - self.y * self.z),
                -squared.x - squared.y + squared.z + squared.w,
            );
            *pitch = atan2_single(n, sqrt_single(p));
            *yaw = atan2_single(
                2.0 * (self.z * self.w - self.x * self.y),
                squared.x - squared.y - squared.z + squared.w,
            );
        } else if n > 0.0 {
            *pitch = std::f32::consts::FRAC_PI_2;
            *yaw = atan2_single(
                self.z * self.w + self.x * self.y,
                0.5 - squared.x - squared.y,
            );
        } else {
            *pitch = -std::f32::consts::FRAC_PI_2;
            *yaw = atan2_single(
                self.z * self.w + self.x * self.y,
                0.5 - squared.x - squared.z,
            );
        }
        Ok(())
    }
    pub fn to_euler_vector(&self) -> Result<Vector3, Error> {
        let (mut roll, mut pitch, mut yaw) = (0.0, 0.0, 0.0);
        self.get_euler_angles(&mut roll, &mut pitch, &mut yaw)?;
        Ok(Vector3 {
            x: roll,
            y: pitch,
            z: yaw,
        })
    }
    pub fn get_axis_angle(&self, axis: &mut Vector3, angle: &mut f32) -> Result<(), Error> {
        let q = Self::normalize_value(*self);
        let sine = sqrt_single(1.0 - q.w * q.w);
        if sine >= 0.001 {
            let mut inverse_sine = 1.0 / sine;
            if q.w < 0.0 {
                inverse_sine = -inverse_sine;
            }
            *axis = Vector3::mul_with_vector3_single(
                Vector3 {
                    x: q.x,
                    y: q.y,
                    z: q.z,
                },
                inverse_sine,
            );
            *angle = 2.0 * acos_single(q.w);
            if *angle > std::f32::consts::PI {
                *angle = 2.0 * std::f32::consts::PI - *angle;
            }
        } else {
            *axis = Vector3::unit_x();
            *angle = 0.0;
        }
        Ok(())
    }

    fn add_value(a: Self, b: Self) -> Self {
        Self {
            x: a.x + b.x,
            y: a.y + b.y,
            z: a.z + b.z,
            w: a.w + b.w,
        }
    }
    fn sub_value(a: Self, b: Self) -> Self {
        Self {
            x: a.x - b.x,
            y: a.y - b.y,
            z: a.z - b.z,
            w: a.w - b.w,
        }
    }
    fn mul_value(a: Self, b: Self) -> Self {
        Self {
            x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
            y: a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z,
            z: a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x,
            w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
        }
    }
    fn mul_scalar(q: Self, scale: f32) -> Self {
        Self {
            x: q.x * scale,
            y: q.y * scale,
            z: q.z * scale,
            w: q.w * scale,
        }
    }
    fn neg_value(q: Self) -> Self {
        Self {
            x: -q.x,
            y: -q.y,
            z: -q.z,
            w: -q.w,
        }
    }
    fn conjugate_value(q: Self) -> Self {
        Self {
            x: -q.x,
            y: -q.y,
            z: -q.z,
            w: q.w,
        }
    }
    fn normalize_value(q: Self) -> Self {
        let magnitude = q.length_value();
        if magnitude > 0.000_000_1 {
            Self::mul_scalar(q, 1.0 / magnitude)
        } else {
            Self::identity()
        }
    }

    pub fn add_with_quaternion_quaternion(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::add_value(a, b))
    }
    pub fn conjugate(q: Self) -> Result<Self, Error> {
        Ok(Self::conjugate_value(q))
    }
    pub fn create_from_axis_angle_with_single_single_single_single(
        x: f32,
        y: f32,
        z: f32,
        angle: f32,
    ) -> Result<Self, Error> {
        Self::create_from_axis_angle_with_vector3_single(Vector3 { x, y, z }, angle)
    }
    pub fn create_from_axis_angle_with_vector3_single(
        axis: Vector3,
        mut angle: f32,
    ) -> Result<Self, Error> {
        let axis = Vector3::normalize(axis)?;
        angle *= 0.5;
        let sine = sin_single(angle);
        Ok(Self::normalize_value(Self {
            x: axis.x * sine,
            y: axis.y * sine,
            z: axis.z * sine,
            w: cos_single(angle),
        }))
    }
    pub fn create_from_eulers_with_vector3(eulers: Vector3) -> Result<Self, Error> {
        Self::create_from_eulers_with_single_single_single(eulers.x, eulers.y, eulers.z)
    }
    pub fn create_from_eulers_with_single_single_single(
        roll: f32,
        pitch: f32,
        yaw: f32,
    ) -> Result<Self, Error> {
        if roll > std::f32::consts::TAU
            || pitch > std::f32::consts::TAU
            || yaw > std::f32::consts::TAU
        {
            return Err(Error::Argument);
        }
        let at_cos = f64::from(roll / 2.0).cos();
        let at_sin = f64::from(roll / 2.0).sin();
        let left_cos = f64::from(pitch / 2.0).cos();
        let left_sin = f64::from(pitch / 2.0).sin();
        let up_cos = f64::from(yaw / 2.0).cos();
        let up_sin = f64::from(yaw / 2.0).sin();
        let at_left_cos = at_cos * left_cos;
        let at_left_sin = at_sin * left_sin;
        Ok(Self {
            x: (at_sin * left_cos * up_cos + at_cos * left_sin * up_sin) as f32,
            y: (at_cos * left_sin * up_cos - at_sin * left_cos * up_sin) as f32,
            z: (at_left_cos * up_sin + at_left_sin * up_cos) as f32,
            w: (at_left_cos * up_cos - at_left_sin * up_sin) as f32,
        })
    }
    pub fn create_from_rotation_matrix(matrix: Matrix4) -> Result<Self, Error> {
        Ok(Self::from_rotation_matrix_value(matrix))
    }
    fn from_rotation_matrix_value(matrix: Matrix4) -> Self {
        let trace = matrix.m11 + matrix.m22 + matrix.m33;
        if trace > 0.0 {
            let mut root = sqrt_single(trace + 1.0);
            let w = root * 0.5;
            root = 0.5 / root;
            return Self {
                x: (matrix.m23 - matrix.m32) * root,
                y: (matrix.m31 - matrix.m13) * root,
                z: (matrix.m12 - matrix.m21) * root,
                w,
            };
        }
        if matrix.m11 >= matrix.m22 && matrix.m11 >= matrix.m33 {
            let root = sqrt_single((1.0 + matrix.m11) - matrix.m22 - matrix.m33);
            let inverse = 0.5 / root;
            return Self {
                x: 0.5 * root,
                y: (matrix.m12 + matrix.m21) * inverse,
                z: (matrix.m13 + matrix.m31) * inverse,
                w: (matrix.m23 - matrix.m32) * inverse,
            };
        }
        if matrix.m22 > matrix.m33 {
            let root = sqrt_single((1.0 + matrix.m22) - matrix.m11 - matrix.m33);
            let inverse = 0.5 / root;
            return Self {
                x: (matrix.m21 + matrix.m12) * inverse,
                y: 0.5 * root,
                z: (matrix.m32 + matrix.m23) * inverse,
                w: (matrix.m31 - matrix.m13) * inverse,
            };
        }
        let root = sqrt_single((1.0 + matrix.m33) - matrix.m11 - matrix.m22);
        let inverse = 0.5 / root;
        Self {
            x: (matrix.m31 + matrix.m13) * inverse,
            y: (matrix.m32 + matrix.m23) * inverse,
            z: 0.5 * root,
            w: (matrix.m12 - matrix.m21) * inverse,
        }
    }
    pub fn divide(q1: Self, q2: Self) -> Result<Self, Error> {
        Ok(Self::mul_value(Self::inverse_value(q1), q2))
    }
    pub fn dot(q1: Self, q2: Self) -> Result<f32, Error> {
        Ok(q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w)
    }
    fn inverse_value(q: Self) -> Self {
        let norm = q.length_squared_value();
        if norm == 0.0 {
            Self::default()
        } else {
            Self::mul_scalar(Self::conjugate_value(q), 1.0 / norm)
        }
    }
    pub fn inverse(q: Self) -> Result<Self, Error> {
        Ok(Self::inverse_value(q))
    }
    pub fn slerp(mut q1: Self, mut q2: Self, amount: f32) -> Result<Self, Error> {
        let mut angle = Self::dot(q1, q2)?;
        if angle < 0.0 {
            q1 = Self::mul_scalar(q1, -1.0);
            angle *= -1.0;
        }
        let (scale, inverse_scale) = if angle + 1.0 > 0.05 {
            if 1.0 - angle >= 0.05 {
                let theta = acos_single(angle);
                let inverse_sine = 1.0 / sin_single(theta);
                (
                    sin_single(theta * (1.0 - amount)) * inverse_sine,
                    sin_single(theta * amount) * inverse_sine,
                )
            } else {
                (1.0 - amount, amount)
            }
        } else {
            q2 = Self {
                x: -q1.y,
                y: q1.x,
                z: -q1.w,
                w: q1.z,
            };
            (
                sin_single(std::f32::consts::PI * (0.5 - amount)),
                sin_single(std::f32::consts::PI * amount),
            )
        };
        Ok(Self::add_value(
            Self::mul_scalar(q1, scale),
            Self::mul_scalar(q2, inverse_scale),
        ))
    }
    pub fn subtract(q1: Self, q2: Self) -> Result<Self, Error> {
        Ok(Self::sub_value(q1, q2))
    }
    pub fn multiply_with_quaternion_quaternion(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::mul_value(a, b))
    }
    pub fn multiply_with_quaternion_single(q: Self, scale: f32) -> Result<Self, Error> {
        Ok(Self::mul_scalar(q, scale))
    }
    pub fn negate(q: Self) -> Result<Self, Error> {
        Ok(Self::neg_value(q))
    }
    pub fn normalize(q: Self) -> Result<Self, Error> {
        Ok(Self::normalize_value(q))
    }
    pub fn parse(val: String) -> Result<Self, Error> {
        let trimmed = val.trim();
        let trimmed = if trimmed.len() >= 2 && trimmed.starts_with('<') && trimmed.ends_with('>') {
            &trimmed[1..trimmed.len() - 1]
        } else {
            trimmed
        };
        let split: Vec<_> = trimmed.split(',').collect();
        match split.len() {
            3 => Self::new_with_single_single_single(
                parse_f32(split[0])?,
                parse_f32(split[1])?,
                parse_f32(split[2])?,
            ),
            4 => Ok(Self {
                x: parse_f32(split[0])?,
                y: parse_f32(split[1])?,
                z: parse_f32(split[2])?,
                w: parse_f32(split[3])?,
            }),
            _ => Err(Error::Argument),
        }
    }
    pub fn try_parse(val: String, result: &mut Self) -> bool {
        match Self::parse(val) {
            Ok(value) => {
                *result = value;
                true
            }
            Err(_) => {
                *result = Self::default();
                false
            }
        }
    }
    pub fn equals_with_quaternion(&self, other: Self) -> bool {
        Self::eq(*self, other)
    }
    pub fn equals_with_object(&self, obj: Option<Object>) -> bool {
        matches!(obj, Some(Object::Quaternion(value)) if Self::eq(*self, value))
    }
    pub fn get_hash_code(&self) -> i32 {
        hash_f32(self.x) ^ hash_f32(self.y) ^ hash_f32(self.z) ^ hash_f32(self.w)
    }
    pub fn to_string(&self) -> String {
        format!(
            "<{}, {}, {}, {}>",
            format_f32(self.x),
            format_f32(self.y),
            format_f32(self.z),
            format_f32(self.w)
        )
    }
    pub fn to_raw_string(&self) -> Result<String, Error> {
        Ok(format!(
            "{:.3} {:.3} {:.3} {:.3}",
            self.x, self.y, self.z, self.w
        ))
    }
    pub fn eq(a: Self, b: Self) -> bool {
        a.w == b.w && a.x == b.x && a.y == b.y && a.z == b.z
    }
    pub fn ne(a: Self, b: Self) -> bool {
        !Self::eq(a, b)
    }
    pub fn add_with_quaternion_quaternion_b0243903(a: Self, b: Self) -> Self {
        Self::add_value(a, b)
    }
    pub fn sub(a: Self, b: Self) -> Self {
        Self::sub_value(a, b)
    }
    pub fn mul_with_quaternion_quaternion(a: Self, b: Self) -> Self {
        Self::mul_value(a, b)
    }
    pub fn mul_with_quaternion_single(q: Self, scale: f32) -> Self {
        Self::mul_scalar(q, scale)
    }
    pub fn div(q1: Self, q2: Self) -> Self {
        Self::mul_value(Self::inverse_value(q1), q2)
    }
    pub fn neg(q: Self) -> Self {
        Self::neg_value(q)
    }
}

/// A row-major 4x4 matrix used with row vectors and translation in row four.
///
/// Matrix products, vector transforms, and axis accessors preserve the exact
/// handedness and row/column conventions of the C# reference.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Matrix4 {
    pub m11: f32,
    pub m12: f32,
    pub m13: f32,
    pub m14: f32,
    pub m21: f32,
    pub m22: f32,
    pub m23: f32,
    pub m24: f32,
    pub m31: f32,
    pub m32: f32,
    pub m33: f32,
    pub m34: f32,
    pub m41: f32,
    pub m42: f32,
    pub m43: f32,
    pub m44: f32,
}

impl Matrix4 {
    #[must_use]
    pub const fn zero() -> Self {
        Self::from_values([0.0; 16])
    }
    #[must_use]
    pub const fn identity() -> Self {
        Self::from_values([
            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
        ])
    }
    const fn from_values(v: [f32; 16]) -> Self {
        Self {
            m11: v[0],
            m12: v[1],
            m13: v[2],
            m14: v[3],
            m21: v[4],
            m22: v[5],
            m23: v[6],
            m24: v[7],
            m31: v[8],
            m32: v[9],
            m33: v[10],
            m34: v[11],
            m41: v[12],
            m42: v[13],
            m43: v[14],
            m44: v[15],
        }
    }
    fn values(self) -> [f32; 16] {
        [
            self.m11, self.m12, self.m13, self.m14, self.m21, self.m22, self.m23, self.m24,
            self.m31, self.m32, self.m33, self.m34, self.m41, self.m42, self.m43, self.m44,
        ]
    }
    pub const fn new_with_matrix4(matrix: Self) -> Result<Self, Error> {
        Ok(matrix)
    }
    pub fn new_with_single_single_single(roll: f32, pitch: f32, yaw: f32) -> Result<Self, Error> {
        Self::create_from_eulers(roll, pitch, yaw)
    }
    pub const fn new_with_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single_single(
        m11: f32,
        m12: f32,
        m13: f32,
        m14: f32,
        m21: f32,
        m22: f32,
        m23: f32,
        m24: f32,
        m31: f32,
        m32: f32,
        m33: f32,
        m34: f32,
        m41: f32,
        m42: f32,
        m43: f32,
        m44: f32,
    ) -> Result<Self, Error> {
        Ok(Self {
            m11,
            m12,
            m13,
            m14,
            m21,
            m22,
            m23,
            m24,
            m31,
            m32,
            m33,
            m34,
            m41,
            m42,
            m43,
            m44,
        })
    }
    pub fn at_axis(&self) -> Vector3 {
        Vector3 {
            x: self.m11,
            y: self.m21,
            z: self.m31,
        }
    }
    pub fn left_axis(&self) -> Vector3 {
        Vector3 {
            x: self.m12,
            y: self.m22,
            z: self.m32,
        }
    }
    pub fn up_axis(&self) -> Vector3 {
        Vector3 {
            x: self.m13,
            y: self.m23,
            z: self.m33,
        }
    }
    pub fn determinant(&mut self) -> Result<f32, Error> {
        Ok(self.determinant_value())
    }
    fn determinant_value(self) -> f32 {
        self.m14 * self.m23 * self.m32 * self.m41
            - self.m13 * self.m24 * self.m32 * self.m41
            - self.m14 * self.m22 * self.m33 * self.m41
            + self.m12 * self.m24 * self.m33 * self.m41
            + self.m13 * self.m22 * self.m34 * self.m41
            - self.m12 * self.m23 * self.m34 * self.m41
            - self.m14 * self.m23 * self.m31 * self.m42
            + self.m13 * self.m24 * self.m31 * self.m42
            + self.m14 * self.m21 * self.m33 * self.m42
            - self.m11 * self.m24 * self.m33 * self.m42
            - self.m13 * self.m21 * self.m34 * self.m42
            + self.m11 * self.m23 * self.m34 * self.m42
            + self.m14 * self.m22 * self.m31 * self.m43
            - self.m12 * self.m24 * self.m31 * self.m43
            - self.m14 * self.m21 * self.m32 * self.m43
            + self.m11 * self.m24 * self.m32 * self.m43
            + self.m12 * self.m21 * self.m34 * self.m43
            - self.m11 * self.m22 * self.m34 * self.m43
            - self.m13 * self.m22 * self.m31 * self.m44
            + self.m12 * self.m23 * self.m31 * self.m44
            + self.m13 * self.m21 * self.m32 * self.m44
            - self.m11 * self.m23 * self.m32 * self.m44
            - self.m12 * self.m21 * self.m33 * self.m44
            + self.m11 * self.m22 * self.m33 * self.m44
    }
    pub fn determinant3x3(&mut self) -> Result<f32, Error> {
        Ok(self.determinant3_value())
    }
    fn determinant3_value(self) -> f32 {
        let d1 = self.m11 * self.m22 * self.m33;
        let d2 = self.m12 * self.m23 * self.m31;
        let d3 = self.m13 * self.m21 * self.m32;
        let d4 = self.m31 * self.m22 * self.m13;
        let d5 = self.m32 * self.m23 * self.m11;
        let d6 = self.m33 * self.m21 * self.m12;
        d1 + d2 + d3 - (d4 + d5 + d6)
    }
    pub fn trace(&mut self) -> Result<f32, Error> {
        Ok(self.m11 + self.m22 + self.m33 + self.m44)
    }
    pub fn get_euler_angles(
        &self,
        roll: &mut f32,
        pitch: &mut f32,
        yaw: &mut f32,
    ) -> Result<(), Error> {
        let mut angle_y = f64::from(clamp_f32(self.m13, -1.0, 1.0)).asin();
        let cosine_y = angle_y.cos();
        let (mut angle_x, mut angle_z);
        if cosine_y.abs() > 0.005 {
            angle_x = f64::from(self.m33) / cosine_y;
            let sine_x = f64::from(-self.m23) / cosine_y;
            angle_x = sine_x.atan2(angle_x);
            let cosine_z = f64::from(self.m11) / cosine_y;
            let sine_z = f64::from(-self.m12) / cosine_y;
            angle_z = sine_z.atan2(cosine_z);
        } else {
            angle_x = 0.0;
            angle_z = f64::from(self.m21).atan2(f64::from(self.m22));
        }
        if angle_x < 0.0 {
            angle_x += 2.0 * std::f64::consts::PI;
        }
        if angle_y < 0.0 {
            angle_y += 2.0 * std::f64::consts::PI;
        }
        if angle_z < 0.0 {
            angle_z += 2.0 * std::f64::consts::PI;
        }
        *roll = angle_x as f32;
        *pitch = angle_y as f32;
        *yaw = angle_z as f32;
        Ok(())
    }
    pub fn get_quaternion(&self) -> Result<Quaternion, Error> {
        let trace = self.m11 + self.m22 + self.m33 + self.m44;
        if trace > f32::from_bits(1) {
            let s = 0.5 / sqrt_single(trace);
            return Ok(Quaternion {
                x: (self.m32 - self.m23) * s,
                y: (self.m13 - self.m31) * s,
                z: (self.m21 - self.m12) * s,
                w: 0.25 / s,
            });
        }
        if self.m11 > self.m22 && self.m11 > self.m33 {
            let s = 2.0 * sqrt_single(1.0 + self.m11 - self.m22 - self.m33);
            return Ok(Quaternion {
                x: 0.25 * s,
                y: (self.m12 + self.m21) / s,
                z: (self.m13 + self.m31) / s,
                w: (self.m23 - self.m32) / s,
            });
        }
        if self.m22 > self.m33 {
            let s = 2.0 * sqrt_single(1.0 + self.m22 - self.m11 - self.m33);
            Ok(Quaternion {
                x: (self.m12 + self.m21) / s,
                y: 0.25 * s,
                z: (self.m23 + self.m32) / s,
                w: (self.m13 - self.m31) / s,
            })
        } else {
            let s = 2.0 * sqrt_single(1.0 + self.m33 - self.m11 - self.m22);
            Ok(Quaternion {
                x: (self.m13 + self.m31) / s,
                y: (self.m23 + self.m32) / s,
                z: 0.25 * s,
                w: (self.m12 - self.m21) / s,
            })
        }
    }
    pub fn decompose(
        &mut self,
        scale: &mut Vector3,
        rotation: &mut Quaternion,
        translation: &mut Vector3,
    ) -> Result<bool, Error> {
        *translation = Vector3 {
            x: self.m41,
            y: self.m42,
            z: self.m43,
        };
        let products = [
            self.m11 * self.m12 * self.m13 * self.m14,
            self.m21 * self.m22 * self.m23 * self.m24,
            self.m31 * self.m32 * self.m33 * self.m34,
        ];
        if products.iter().any(|v| v.is_nan()) {
            return Err(Error::Argument);
        }
        let signs = products.map(|v| if v < 0.0 { -1.0 } else { 1.0 });
        let sx =
            signs[0] * sqrt_single(self.m11 * self.m11 + self.m12 * self.m12 + self.m13 * self.m13);
        let sy =
            signs[1] * sqrt_single(self.m21 * self.m21 + self.m22 * self.m22 + self.m23 * self.m23);
        let sz =
            signs[2] * sqrt_single(self.m31 * self.m31 + self.m32 * self.m32 + self.m33 * self.m33);
        *scale = Vector3 {
            x: sx,
            y: sy,
            z: sz,
        };
        if sx == 0.0 || sy == 0.0 || sz == 0.0 {
            *rotation = Quaternion::identity();
            return Ok(false);
        }
        let normalized = Self::from_values([
            self.m11 / sx,
            self.m12 / sx,
            self.m13 / sx,
            0.0,
            self.m21 / sy,
            self.m22 / sy,
            self.m23 / sy,
            0.0,
            self.m31 / sz,
            self.m32 / sz,
            self.m33 / sz,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ]);
        *rotation = Quaternion::from_rotation_matrix_value(normalized);
        Ok(true)
    }

    fn component_map(a: Self, b: Self, op: impl Fn(f32, f32) -> f32) -> Self {
        let left = a.values();
        let right = b.values();
        let mut output = [0.0; 16];
        for index in 0..16 {
            output[index] = op(left[index], right[index]);
        }
        Self::from_values(output)
    }
    fn scalar_map(value: Self, scalar: f32, op: impl Fn(f32, f32) -> f32) -> Self {
        let input = value.values();
        let mut output = [0.0; 16];
        for index in 0..16 {
            output[index] = op(input[index], scalar);
        }
        Self::from_values(output)
    }
    pub fn add_with_matrix4_matrix4(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::component_map(a, b, |x, y| x + y))
    }
    pub fn create_from_axis_angle(axis: Vector3, angle: f32) -> Result<Self, Error> {
        let x = axis.x;
        let y = axis.y;
        let z = axis.z;
        let sine = sin_single(angle);
        let cosine = cos_single(angle);
        let xx = x * x;
        let yy = y * y;
        let zz = z * z;
        let xy = x * y;
        let xz = x * z;
        let yz = y * z;
        Ok(Self::from_values([
            xx + cosine * (1.0 - xx),
            (xy - cosine * xy) + sine * z,
            (xz - cosine * xz) - sine * y,
            0.0,
            (xy - cosine * xy) - sine * z,
            yy + cosine * (1.0 - yy),
            (yz - cosine * yz) + sine * x,
            0.0,
            (xz - cosine * xz) + sine * y,
            (yz - cosine * yz) - sine * x,
            zz + cosine * (1.0 - zz),
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ]))
    }
    pub fn create_from_eulers(roll: f32, pitch: f32, yaw: f32) -> Result<Self, Error> {
        let a = cos_single(roll);
        let b = sin_single(roll);
        let c = cos_single(pitch);
        let d = sin_single(pitch);
        let e = cos_single(yaw);
        let f = sin_single(yaw);
        let ad = a * d;
        let bd = b * d;
        Ok(Self::from_values([
            c * e,
            -c * f,
            d,
            0.0,
            bd * e + a * f,
            -bd * f + a * e,
            -b * c,
            0.0,
            -ad * e + b * f,
            ad * f + b * e,
            a * c,
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ]))
    }
    pub fn create_from_quaternion(q: Quaternion) -> Result<Self, Error> {
        let xx = q.x * q.x;
        let yy = q.y * q.y;
        let zz = q.z * q.z;
        let xy = q.x * q.y;
        let zw = q.z * q.w;
        let zx = q.z * q.x;
        let yw = q.y * q.w;
        let yz = q.y * q.z;
        let xw = q.x * q.w;
        Ok(Self::from_values([
            1.0 - 2.0 * (yy + zz),
            2.0 * (xy + zw),
            2.0 * (zx - yw),
            0.0,
            2.0 * (xy - zw),
            1.0 - 2.0 * (zz + xx),
            2.0 * (yz + xw),
            0.0,
            2.0 * (zx + yw),
            2.0 * (yz - xw),
            1.0 - 2.0 * (yy + xx),
            0.0,
            0.0,
            0.0,
            0.0,
            1.0,
        ]))
    }
    pub fn create_look_at(position: Vector3, target: Vector3, up: Vector3) -> Result<Self, Error> {
        let z = Vector3::normalize(Vector3::sub(position, target))?;
        let x = Vector3::normalize(Vector3::cross(up, z)?)?;
        let y = Vector3::cross(z, x)?;
        Ok(Self::from_values([
            x.x,
            y.x,
            z.x,
            0.0,
            x.y,
            y.y,
            z.y,
            0.0,
            x.z,
            y.z,
            z.z,
            0.0,
            -Vector3::dot(x, position)?,
            -Vector3::dot(y, position)?,
            -Vector3::dot(z, position)?,
            1.0,
        ]))
    }
    pub fn create_rotation_x(radians: f32) -> Result<Self, Error> {
        let cosine = cos_single(radians);
        let sine = sin_single(radians);
        Ok(Self::from_values([
            1.0, 0.0, 0.0, 0.0, 0.0, cosine, sine, 0.0, 0.0, -sine, cosine, 0.0, 0.0, 0.0, 0.0, 1.0,
        ]))
    }
    pub fn create_rotation_y(radians: f32) -> Result<Self, Error> {
        let cosine = cos_single(radians);
        let sine = sin_single(radians);
        Ok(Self::from_values([
            cosine, 0.0, -sine, 0.0, 0.0, 1.0, 0.0, 0.0, sine, 0.0, cosine, 0.0, 0.0, 0.0, 0.0, 1.0,
        ]))
    }
    pub fn create_rotation_z(radians: f32) -> Result<Self, Error> {
        let cosine = cos_single(radians);
        let sine = sin_single(radians);
        Ok(Self::from_values([
            cosine, sine, 0.0, 0.0, -sine, cosine, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
        ]))
    }
    pub fn create_scale(scale: Vector3) -> Result<Self, Error> {
        Ok(Self::from_values([
            scale.x, 0.0, 0.0, 0.0, 0.0, scale.y, 0.0, 0.0, 0.0, 0.0, scale.z, 0.0, 0.0, 0.0, 0.0,
            1.0,
        ]))
    }
    pub fn create_translation(position: Vector3) -> Result<Self, Error> {
        Ok(Self::from_values([
            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, position.x, position.y,
            position.z, 1.0,
        ]))
    }
    pub fn create_world(position: Vector3, forward: Vector3, up: Vector3) -> Result<Self, Error> {
        let forward = Vector3::normalize(forward)?;
        let right = Vector3::normalize(Vector3::cross(forward, up)?)?;
        let up = Vector3::normalize(Vector3::cross(right, forward)?)?;
        Ok(Self::from_values([
            right.x, right.y, right.z, 0.0, up.x, up.y, up.z, 0.0, -forward.x, -forward.y,
            -forward.z, 0.0, position.x, position.y, position.z, 1.0,
        ]))
    }
    pub fn divide_with_matrix4_matrix4(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::component_map(a, b, |x, y| x / y))
    }
    pub fn divide_with_matrix4_single(a: Self, divider: f32) -> Result<Self, Error> {
        Ok(Self::scalar_map(a, 1.0 / divider, |x, y| x * y))
    }
    pub fn lerp(a: Self, b: Self, amount: f32) -> Result<Self, Error> {
        Ok(Self::component_map(a, b, |x, y| x + (y - x) * amount))
    }
    fn multiply_value(a: Self, b: Self) -> Self {
        let left = a.values();
        let right = b.values();
        let mut output = [0.0; 16];
        for row in 0..4 {
            for column in 0..4 {
                output[row * 4 + column] = left[row * 4] * right[column]
                    + left[row * 4 + 1] * right[4 + column]
                    + left[row * 4 + 2] * right[8 + column]
                    + left[row * 4 + 3] * right[12 + column];
            }
        }
        Self::from_values(output)
    }
    pub fn multiply_with_matrix4_matrix4(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::multiply_value(a, b))
    }
    pub fn multiply_with_matrix4_single(a: Self, scale: f32) -> Result<Self, Error> {
        Ok(Self::scalar_map(a, scale, |x, y| x * y))
    }
    pub fn negate(matrix: Self) -> Result<Self, Error> {
        Ok(Self::scalar_map(matrix, -1.0, |x, y| x * y))
    }
    pub fn subtract(a: Self, b: Self) -> Result<Self, Error> {
        Ok(Self::component_map(a, b, |x, y| x - y))
    }
    pub fn transform(value: Self, rotation: Quaternion) -> Result<Self, Error> {
        let x2 = rotation.x + rotation.x;
        let y2 = rotation.y + rotation.y;
        let z2 = rotation.z + rotation.z;
        let a = (1.0 - rotation.y * y2) - rotation.z * z2;
        let b = rotation.x * y2 - rotation.w * z2;
        let c = rotation.x * z2 + rotation.w * y2;
        let d = rotation.x * y2 + rotation.w * z2;
        let e = (1.0 - rotation.x * x2) - rotation.z * z2;
        let f = rotation.y * z2 - rotation.w * x2;
        let g = rotation.x * z2 - rotation.w * y2;
        let h = rotation.y * z2 + rotation.w * x2;
        let i = (1.0 - rotation.x * x2) - rotation.y * y2;
        Ok(Self::from_values([
            (value.m11 * a + value.m12 * b) + value.m13 * c,
            (value.m11 * d + value.m12 * e) + value.m13 * f,
            (value.m11 * g + value.m12 * h) + value.m13 * i,
            value.m14,
            (value.m21 * a + value.m22 * b) + value.m23 * c,
            (value.m21 * d + value.m22 * e) + value.m23 * f,
            (value.m21 * g + value.m22 * h) + value.m23 * i,
            value.m24,
            (value.m31 * a + value.m32 * b) + value.m33 * c,
            (value.m31 * d + value.m32 * e) + value.m33 * f,
            (value.m31 * g + value.m32 * h) + value.m33 * i,
            value.m34,
            (value.m41 * a + value.m42 * b) + value.m43 * c,
            (value.m41 * d + value.m42 * e) + value.m43 * f,
            (value.m41 * g + value.m42 * h) + value.m43 * i,
            value.m44,
        ]))
    }
    pub fn transpose(matrix: Self) -> Result<Self, Error> {
        Ok(Self::from_values([
            matrix.m11, matrix.m21, matrix.m31, matrix.m41, matrix.m12, matrix.m22, matrix.m32,
            matrix.m42, matrix.m13, matrix.m23, matrix.m33, matrix.m43, matrix.m14, matrix.m24,
            matrix.m34, matrix.m44,
        ]))
    }
    fn minor_value(matrix: Self, row: i32, column: i32) -> Self {
        let values = matrix.values();
        let mut minor = [0.0; 16];
        let mut target_row = 0;
        for (source_row, source_row_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() {
            if source_row_i32 == row {
                continue;
            }
            let mut target_column = 0;
            for (source_column, source_column_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() {
                if source_column_i32 == column {
                    continue;
                }
                minor[target_row * 4 + target_column] = values[source_row * 4 + source_column];
                target_column += 1;
            }
            target_row += 1;
        }
        Self::from_values([
            minor[0], minor[1], minor[2], minor[3], minor[4], minor[5], minor[6], minor[7],
            minor[8], minor[9], minor[10], minor[11], 0.0, 0.0, 0.0, 0.0,
        ])
    }
    pub fn minor(matrix: Self, row: i32, col: i32) -> Result<Self, Error> {
        Ok(Self::minor_value(matrix, row, col))
    }
    fn adjoint_value(matrix: Self) -> Self {
        let mut cofactors = [0.0; 16];
        for (row, row_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() {
            for (column, column_i32) in [0_i32, 1, 2, 3].into_iter().enumerate() {
                let sign = if (row + column) % 2 == 0 { 1.0 } else { -1.0 };
                cofactors[row * 4 + column] =
                    sign * Self::minor_value(matrix, row_i32, column_i32).determinant3_value();
            }
        }
        let cofactor_matrix = Self::from_values(cofactors);
        Self::from_values([
            cofactor_matrix.m11,
            cofactor_matrix.m21,
            cofactor_matrix.m31,
            cofactor_matrix.m41,
            cofactor_matrix.m12,
            cofactor_matrix.m22,
            cofactor_matrix.m32,
            cofactor_matrix.m42,
            cofactor_matrix.m13,
            cofactor_matrix.m23,
            cofactor_matrix.m33,
            cofactor_matrix.m43,
            cofactor_matrix.m14,
            cofactor_matrix.m24,
            cofactor_matrix.m34,
            cofactor_matrix.m44,
        ])
    }
    pub fn adjoint(matrix: Self) -> Result<Self, Error> {
        Ok(Self::adjoint_value(matrix))
    }
    pub fn adjoint3x3(matrix: Self) -> Result<Self, Error> {
        Ok(Self::adjoint_value(matrix))
    }
    pub fn inverse(matrix: Self) -> Result<Self, Error> {
        let determinant = matrix.determinant_value();
        if determinant == 0.0 {
            return Err(Error::Argument);
        }
        Ok(Self::scalar_map(
            Self::adjoint_value(matrix),
            1.0 / determinant,
            |x, y| x * y,
        ))
    }
    pub fn inverse3x3(matrix: Self) -> Result<Self, Error> {
        let determinant = matrix.determinant3_value();
        if determinant == 0.0 {
            return Err(Error::Argument);
        }
        Ok(Self::scalar_map(
            Self::adjoint_value(matrix),
            1.0 / determinant,
            |x, y| x * y,
        ))
    }
    pub fn equals_with_matrix4(&self, other: Self) -> bool {
        Self::eq(*self, other)
    }
    pub fn equals_with_object(&self, obj: Option<Object>) -> bool {
        matches!(obj,Some(Object::Matrix4(value))if Self::eq(*self,value))
    }
    pub fn get_hash_code(&self) -> i32 {
        self.values()
            .into_iter()
            .fold(0, |hash, value| hash ^ hash_f32(value))
    }
    pub fn to_string(&self) -> String {
        let v = self.values().map(format_f32);
        format!(
            "|{}, {}, {}, {}|\n|{}, {}, {}, {}|\n|{}, {}, {}, {}|\n|{}, {}, {}, {}|",
            v[0],
            v[1],
            v[2],
            v[3],
            v[4],
            v[5],
            v[6],
            v[7],
            v[8],
            v[9],
            v[10],
            v[11],
            v[12],
            v[13],
            v[14],
            v[15]
        )
    }
    /// Returns the requested row in storage order.
    ///
    /// # Panics
    ///
    /// Panics when `row` is outside the reference indexer range `0..=3`.
    pub fn item_with_int32(&self, row: i32) -> Vector4 {
        match row {
            0 => Vector4 {
                x: self.m11,
                y: self.m12,
                z: self.m13,
                w: self.m14,
            },
            1 => Vector4 {
                x: self.m21,
                y: self.m22,
                z: self.m23,
                w: self.m24,
            },
            2 => Vector4 {
                x: self.m31,
                y: self.m32,
                z: self.m33,
                w: self.m34,
            },
            3 => Vector4 {
                x: self.m41,
                y: self.m42,
                z: self.m43,
                w: self.m44,
            },
            _ => panic!("Matrix4 row index must be from 0-3"),
        }
    }
    /// Returns the component at a zero-based row and column.
    ///
    /// # Panics
    ///
    /// Panics when either coordinate is outside the reference range `0..=3`.
    pub fn item_with_int32_int32(&self, row: i32, column: i32) -> f32 {
        match (row, column) {
            (0, 0) => self.m11,
            (0, 1) => self.m12,
            (0, 2) => self.m13,
            (0, 3) => self.m14,
            (1, 0) => self.m21,
            (1, 1) => self.m22,
            (1, 2) => self.m23,
            (1, 3) => self.m24,
            (2, 0) => self.m31,
            (2, 1) => self.m32,
            (2, 2) => self.m33,
            (2, 3) => self.m34,
            (3, 0) => self.m41,
            (3, 1) => self.m42,
            (3, 2) => self.m43,
            (3, 3) => self.m44,
            _ => panic!("Matrix4 row and column values must be from 0-3"),
        }
    }
    pub fn eq(left: Self, right: Self) -> bool {
        left.values()
            .into_iter()
            .zip(right.values())
            .all(|(a, b)| a == b)
    }
    pub fn ne(left: Self, right: Self) -> bool {
        !Self::eq(left, right)
    }
    pub fn add_with_matrix4_matrix4_31d5e46d(left: Self, right: Self) -> Self {
        Self::component_map(left, right, |a, b| a + b)
    }
    pub fn sub(left: Self, right: Self) -> Self {
        Self::component_map(left, right, |a, b| a - b)
    }
    pub fn mul_with_matrix4_matrix4(left: Self, right: Self) -> Self {
        Self::multiply_value(left, right)
    }
    pub fn mul_with_matrix4_single(left: Self, scalar: f32) -> Self {
        Self::scalar_map(left, scalar, |a, b| a * b)
    }
    pub fn div_with_matrix4_matrix4(left: Self, right: Self) -> Self {
        Self::component_map(left, right, |a, b| a / b)
    }
    pub fn div_with_matrix4_single(matrix: Self, divider: f32) -> Self {
        Self::scalar_map(matrix, 1.0 / divider, |a, b| a * b)
    }
    pub fn neg(matrix: Self) -> Self {
        Self::scalar_map(matrix, -1.0, |a, b| a * b)
    }
}

#[cfg(test)]
mod tests {
    use super::{Matrix4, Quaternion, sqrt_single};
    use crate::{Error, Vector3, Vector4};

    fn assert_close(actual: f32, expected: f32) {
        assert!(
            (actual - expected).abs() <= 0.000_01,
            "expected {expected}, got {actual}"
        );
    }

    fn assert_vector_close(actual: Vector3, expected: Vector3) {
        assert_close(actual.x, expected.x);
        assert_close(actual.y, expected.y);
        assert_close(actual.z, expected.z);
    }

    fn assert_matrix_close(actual: Matrix4, expected: Matrix4) {
        for (actual, expected) in actual.values().into_iter().zip(expected.values()) {
            assert_close(actual, expected);
        }
    }

    #[test]
    fn quaternion_bytes_preserve_reference_normalization_and_write_order() {
        let value = Quaternion {
            x: 2.0,
            y: -3.0,
            z: 4.0,
            w: -5.0,
        };
        let bytes = value.get_bytes().unwrap();
        assert_eq!(bytes.len(), 12);
        let decoded = Quaternion::from_bytes(bytes, 0, true).unwrap();
        let norm = sqrt_single(54.0);
        assert_close(decoded.x, -2.0 / norm);
        assert_close(decoded.y, 3.0 / norm);
        assert_close(decoded.z, -4.0 / norm);
        assert!(decoded.w >= 0.0);

        let raw = [1.0_f32, 2.0, 3.0, 4.0]
            .into_iter()
            .flat_map(f32::to_le_bytes)
            .collect();
        assert_eq!(
            Quaternion::from_bytes(raw, 0, false).unwrap(),
            Quaternion {
                x: 1.0,
                y: 2.0,
                z: 3.0,
                w: 4.0
            }
        );

        let mut short = [0_u8; 8];
        assert_eq!(value.to_bytes(&mut short, 0), Err(Error::IndexOutOfRange));
        assert_ne!(short, [0; 8]);
        assert_eq!(
            Quaternion::default().to_bytes(&mut [0; 12], 0),
            Err(Error::InvalidOperation)
        );
    }

    #[test]
    fn quaternion_rotation_multiplication_and_inverse_match_reference_direction() {
        let quarter_turn = Quaternion::create_from_axis_angle_with_vector3_single(
            Vector3::unit_z(),
            std::f32::consts::FRAC_PI_2,
        )
        .unwrap();
        let rotated = Vector3::mul_with_vector3_quaternion(Vector3::unit_x(), quarter_turn);
        assert_vector_close(rotated, Vector3::unit_y());

        let matrix = Matrix4::create_from_quaternion(quarter_turn).unwrap();
        assert_vector_close(
            Vector3::transform_normal(Vector3::unit_x(), matrix).unwrap(),
            Vector3::unit_y(),
        );
        let recovered = Quaternion::create_from_rotation_matrix(matrix).unwrap();
        assert_close(Quaternion::dot(quarter_turn, recovered).unwrap().abs(), 1.0);

        assert_matrix_close(
            Matrix4::mul_with_matrix4_matrix4(
                matrix,
                Matrix4::create_rotation_z(-std::f32::consts::FRAC_PI_2).unwrap(),
            ),
            Matrix4::identity(),
        );
        assert_eq!(
            Quaternion::mul_with_quaternion_quaternion(
                quarter_turn,
                Quaternion::inverse(quarter_turn).unwrap(),
            ),
            Quaternion::identity()
        );
    }

    #[test]
    fn quaternion_euler_axis_slerp_and_parsing_cover_edge_behaviors() {
        let eulers = Vector3 {
            x: 0.25,
            y: 0.5,
            z: 0.75,
        };
        let value = Quaternion::create_from_eulers_with_vector3(eulers).unwrap();
        let matrix = Matrix4::create_from_eulers(eulers.x, eulers.y, eulers.z).unwrap();
        assert_close(
            Quaternion::dot(value, matrix.get_quaternion().unwrap())
                .unwrap()
                .abs(),
            1.0,
        );

        let mut axis = Vector3::zero();
        let mut angle = 0.0;
        value.get_axis_angle(&mut axis, &mut angle).unwrap();
        let from_axis =
            Quaternion::create_from_axis_angle_with_vector3_single(axis, angle).unwrap();
        assert_close(Quaternion::dot(value, from_axis).unwrap().abs(), 1.0);

        let halfway = Quaternion::slerp(Quaternion::identity(), value, 0.5).unwrap();
        assert_close(halfway.length_value(), 1.0);
        assert_eq!(
            Quaternion::create_from_eulers_with_single_single_single(
                std::f32::consts::TAU + 0.1,
                0.0,
                0.0
            ),
            Err(Error::Argument)
        );

        let parsed = Quaternion::parse(" <1, 2, 3, 4> ".to_owned()).unwrap();
        assert_eq!(parsed.to_string(), "<1, 2, 3, 4>");
        assert_eq!(parsed.to_raw_string().unwrap(), "1.000 2.000 3.000 4.000");
        let mut output = Quaternion::identity();
        assert!(!Quaternion::try_parse("1, 2".to_owned(), &mut output));
        assert_eq!(output, Quaternion::default());
    }

    #[test]
    fn matrix_transform_chain_inverse_and_conventions_are_compatible() {
        let scale = Matrix4::create_scale(Vector3 {
            x: 2.0,
            y: 3.0,
            z: 4.0,
        })
        .unwrap();
        let rotation = Matrix4::create_rotation_z(std::f32::consts::FRAC_PI_2).unwrap();
        let translation = Matrix4::create_translation(Vector3 {
            x: 10.0,
            y: 20.0,
            z: 30.0,
        })
        .unwrap();
        let transform = Matrix4::mul_with_matrix4_matrix4(
            Matrix4::mul_with_matrix4_matrix4(scale, rotation),
            translation,
        );
        assert_vector_close(
            Vector3::transform(Vector3::unit_x(), transform).unwrap(),
            Vector3 {
                x: 10.0,
                y: 22.0,
                z: 30.0,
            },
        );
        assert_eq!(
            transform.item_with_int32(3),
            Vector4 {
                x: 10.0,
                y: 20.0,
                z: 30.0,
                w: 1.0
            }
        );
        assert_eq!(transform.item_with_int32_int32(3, 1), 20.0);

        let inverse = Matrix4::inverse(transform).unwrap();
        assert_matrix_close(
            Matrix4::multiply_with_matrix4_matrix4(transform, inverse).unwrap(),
            Matrix4::identity(),
        );
        assert_eq!(Matrix4::inverse(Matrix4::zero()), Err(Error::Argument));
    }

    #[test]
    fn matrix_decomposition_and_rotation_transform_round_trip() {
        let expected_scale = Vector3 {
            x: 2.0,
            y: 3.0,
            z: 4.0,
        };
        let expected_rotation =
            Quaternion::create_from_eulers_with_single_single_single(0.25, 0.5, 0.75).unwrap();
        let expected_translation = Vector3 {
            x: 7.0,
            y: 8.0,
            z: 9.0,
        };
        let mut value = Matrix4::mul_with_matrix4_matrix4(
            Matrix4::mul_with_matrix4_matrix4(
                Matrix4::create_scale(expected_scale).unwrap(),
                Matrix4::create_from_quaternion(expected_rotation).unwrap(),
            ),
            Matrix4::create_translation(expected_translation).unwrap(),
        );
        let (mut scale, mut rotation, mut translation) =
            (Vector3::zero(), Quaternion::default(), Vector3::zero());
        assert!(
            value
                .decompose(&mut scale, &mut rotation, &mut translation)
                .unwrap()
        );
        assert_vector_close(scale, expected_scale);
        assert_vector_close(translation, expected_translation);
        assert_close(
            Quaternion::dot(rotation, expected_rotation).unwrap().abs(),
            1.0,
        );

        let rotated = Matrix4::transform(Matrix4::identity(), expected_rotation).unwrap();
        assert_matrix_close(
            rotated,
            Matrix4::create_from_quaternion(expected_rotation).unwrap(),
        );

        let mut singular = Matrix4::zero();
        assert!(
            !singular
                .decompose(&mut scale, &mut rotation, &mut translation)
                .unwrap()
        );
        assert_eq!(rotation, Quaternion::identity());
    }

    #[test]
    fn matrix_axes_minors_format_and_hash_follow_component_storage() {
        let matrix = Matrix4::from_values([
            1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
        ]);
        assert_eq!(
            matrix.at_axis(),
            Vector3 {
                x: 1.0,
                y: 5.0,
                z: 9.0
            }
        );
        assert_eq!(
            matrix.left_axis(),
            Vector3 {
                x: 2.0,
                y: 6.0,
                z: 10.0
            }
        );
        assert_eq!(
            matrix.up_axis(),
            Vector3 {
                x: 3.0,
                y: 7.0,
                z: 11.0
            }
        );
        assert_eq!(
            Matrix4::minor(matrix, 0, 0).unwrap().item_with_int32(0),
            Vector4 {
                x: 6.0,
                y: 7.0,
                z: 8.0,
                w: 0.0
            }
        );
        assert!(matrix.to_string().starts_with("|1, 2, 3, 4|\n|5, 6, 7, 8|"));
        assert_eq!(
            matrix.get_hash_code(),
            Matrix4::new_with_matrix4(matrix).unwrap().get_hash_code()
        );
    }

    #[test]
    #[should_panic(expected = "Matrix4 row and column values must be from 0-3")]
    fn matrix_indexer_rejects_invalid_coordinates_like_reference() {
        Matrix4::identity().item_with_int32_int32(4, 0);
    }
}
