//! # Mat2
//!
//! A 2x2 column-major matrix.
//!
//! # Conventions
//!
//! - Column-major
//! - Stored as 2 columns `col0` and `col1`, each a [`Vec2`]
//!
//! # Examples
//!
//! ```rust
//! # use roofy::{Mat2, Vec2};
//!
//! let m: Mat2 = Mat2::IDENTITY;
//! let scaled: Mat2 = m * Mat2::from_diagonal(Vec2::splat(2.0));
//!
//! assert_eq!(scaled.col0.x, 2.0);
//! assert_eq!(scaled.col1.y, 2.0);
//! ```

use std::{
    fmt::Display,
    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};

use crate::{Mat3, Vec2};

/// A 2x2 column-major matrix.
///
/// Stored as 2 columns `col0` and `col1`, each a [`Vec2`]
#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(C)]
pub struct Mat2 {
    /// Column 0 of the matrix.
    pub col0: Vec2,
    /// Column 1 of the matrix.
    pub col1: Vec2,
}

impl Mat2 {
    /// Threshold for treating a value as zero.
    pub const EPSILON: f32 = 1e-6;

    /// A 2x2 matrix with all components `NaN`.
    pub const NAN: Self = Self::from_cols(Vec2::NAN, Vec2::NAN);

    /// A 2x2 matrix with all components `0.0`.
    pub const ZERO: Self = Self::from_cols(Vec2::ZERO, Vec2::ZERO);

    /// A 2x2 matrix, where all diagonal components are `1.0` and all others are `0.0`.
    pub const IDENTITY: Self = Self::from_cols(Vec2::X, Vec2::Y);

    /// Creates a `Mat2` from 4 `f32`, in column-major order.
    ///
    /// # Convention
    /// Parameters are named `mCR` where `C` is the column index and `R` is the row index.
    #[inline(always)]
    #[must_use]
    pub const fn new(m00: f32, m01: f32, m10: f32, m11: f32) -> Self {
        Self::from_cols(Vec2::new(m00, m01), Vec2::new(m10, m11))
    }

    /// Creates a `Mat2` from 2 column `Vec2`.
    #[inline(always)]
    #[must_use]
    pub const fn from_cols(col0: Vec2, col1: Vec2) -> Self {
        Self { col0, col1 }
    }

    /// Creates a `Mat2` with diagonal set to `v` and all other components `0.0`.
    #[inline]
    #[must_use]
    pub const fn from_diagonal(v: Vec2) -> Self {
        Self::new(v.x, 0.0, 0.0, v.y)
    }

    /// Creates a `Mat2` from a `[f32; 4]`, in column-major order:
    /// `[col0.x, col0.y, col1.x, col1.y]`.
    #[inline]
    #[must_use]
    pub const fn from_array(a: [f32; 4]) -> Self {
        Self::new(a[0], a[1], a[2], a[3])
    }

    /// Returns a copy of the components as `[col0, col1]`,
    /// with each column as `[x, y]`, in column-major order.
    #[inline]
    #[must_use]
    pub const fn to_array(self) -> [f32; 4] {
        [self.col0.x, self.col0.y, self.col1.x, self.col1.y]
    }

    /// Creates a `Mat2` from first 4 values of `slice`, in column-major order.
    ///
    /// # Panics
    /// Panics if `slice` is less than 4 elements long.
    #[inline]
    #[must_use]
    pub const fn from_slice(slice: &[f32]) -> Self {
        Self::new(slice[0], slice[1], slice[2], slice[3])
    }

    /// Writes the columns of `self` to the first 4 elements of `slice`.
    ///
    /// # Panics
    /// Panics if `slice` is less than 4 elements long.
    #[inline]
    pub const fn write_to_slice(self, slice: &mut [f32]) {
        slice[0] = self.col0.x;
        slice[1] = self.col0.y;
        slice[2] = self.col1.x;
        slice[3] = self.col1.y;
    }

    /// Creates a `Mat2` from a `Mat3`, discarding `col2` and `row2`.
    #[inline]
    #[must_use]
    pub const fn from_mat3(m: Mat3) -> Self {
        Self::from_cols(m.col0.to_vec2(), m.col1.to_vec2())
    }

    /// Creates a `Mat2` that rotates by `angle` in radians.
    #[inline]
    #[must_use]
    pub fn from_angle(angle: f32) -> Self {
        let (sin, cos): (f32, f32) = angle.sin_cos();
        Self::new(cos, sin, -sin, cos)
    }

    /// Creates a `Mat2` that rotates by `angle` in degrees.
    #[inline]
    #[must_use]
    pub fn from_angle_degrees(angle: f32) -> Self {
        Self::from_angle(angle.to_radians())
    }

    /// Creates a `Mat2` that scales by `scale` then rotates by `angle` in radians.
    #[inline]
    #[must_use]
    pub fn from_scale_angle(scale: Vec2, angle: f32) -> Self {
        let (sin, cos): (f32, f32) = angle.sin_cos();
        Self::new(cos * scale.x, sin * scale.x, -sin * scale.y, cos * scale.y)
    }

    /// Creates a `Mat2` that scales by `scale` then rotates by `angle` in degrees.
    #[inline]
    #[must_use]
    pub fn from_scale_angle_degrees(scale: Vec2, angle: f32) -> Self {
        Self::from_scale_angle(scale, angle.to_radians())
    }

    /// Returns the component-wise sum of `self` and `rhs`.
    #[inline]
    #[must_use]
    pub const fn add(self, rhs: Self) -> Self {
        Self::from_cols(self.col0.add(rhs.col0), self.col1.add(rhs.col1))
    }

    /// Sets self to the component-wise sum of `self` and `rhs`.
    #[inline]
    pub const fn add_assign(&mut self, rhs: Self) {
        self.col0.add_assign(rhs.col0);
        self.col1.add_assign(rhs.col1);
    }

    /// Returns the component-wise difference of `self` and `rhs`.
    #[inline]
    #[must_use]
    pub const fn sub(self, rhs: Self) -> Self {
        Self::from_cols(self.col0.sub(rhs.col0), self.col1.sub(rhs.col1))
    }

    /// Sets self to the component-wise difference of `self` and `rhs`.
    #[inline]
    pub const fn sub_assign(&mut self, rhs: Self) {
        self.col0.sub_assign(rhs.col0);
        self.col1.sub_assign(rhs.col1);
    }

    /// Returns the matrix product of `self` and `rhs`.
    #[inline]
    #[must_use]
    pub const fn mul(self, rhs: Self) -> Self {
        Self::from_cols(self.mul_vec2(rhs.col0), self.mul_vec2(rhs.col1))
    }

    /// Sets `self` to the matrix product of `self` and `rhs`.
    #[inline]
    pub const fn mul_assign(&mut self, rhs: Self) {
        *self = self.mul(rhs);
    }

    /// Returns `rhs` transformed by `self`.
    ///
    /// Equivalent to [`transform`](Self::transform()).
    #[inline]
    #[must_use]
    pub const fn mul_vec2(self, rhs: Vec2) -> Vec2 {
        self.transform(rhs)
    }

    /// Returns `self` with each component multiplied by `rhs`.
    #[inline]
    #[must_use]
    pub const fn mul_scalar(self, rhs: f32) -> Self {
        Self::from_cols(self.col0.mul_scalar(rhs), self.col1.mul_scalar(rhs))
    }

    /// Sets `self` to `self` with each component multiplied by `rhs`.
    #[inline]
    pub const fn mul_assign_scalar(&mut self, rhs: f32) {
        *self = self.mul_scalar(rhs);
    }

    /// Returns `self` with each component divided by `rhs`.
    #[inline]
    #[must_use]
    pub const fn div_scalar(self, rhs: f32) -> Self {
        Self::from_cols(self.col0.div_scalar(rhs), self.col1.div_scalar(rhs))
    }

    /// Sets `self` to `self` with each component divided by `rhs`.
    #[inline]
    pub const fn div_assign_scalar(&mut self, rhs: f32) {
        *self = self.div_scalar(rhs);
    }

    /// Returns `self` with negated components.
    #[inline]
    #[must_use]
    pub const fn neg(self) -> Self {
        Self::from_cols(self.col0.neg(), self.col1.neg())
    }

    /// Returns a reference to the components as `[col0, col1]`,
    /// with each column as `[x, y]`.
    ///
    /// # Safety
    /// Relies on `Mat2` being `#[repr(C)]`
    ///
    /// # Convention
    /// Array is in column-major order.
    #[inline]
    #[must_use]
    pub const fn as_ref(&self) -> &[f32; 4] {
        unsafe { &*(self as *const Mat2 as *const [f32; 4]) }
    }

    /// Returns a mutable reference to the components as `[col0, col1]`,
    /// with each column as `[x, y]`.
    ///
    /// # Safety
    /// Relies on `Mat2` being `#[repr(C)]`
    ///
    /// # Convention
    /// Array is in column-major order.
    #[inline]
    #[must_use]
    pub const fn as_mut(&mut self) -> &mut [f32; 4] {
        unsafe { &mut *(self as *mut Mat2 as *mut [f32; 4]) }
    }

    /// Returns all columns of `self`.
    #[inline]
    #[must_use]
    pub const fn cols(self) -> [Vec2; 2] {
        [self.col0, self.col1]
    }

    /// Returns column of `self` at `index`.
    ///
    /// `0` maps to `col0`, `1` maps to `col1`.
    ///
    /// # Panics
    /// Panics if `index` is greater than `1`.
    #[inline]
    pub const fn col(self, index: usize) -> Vec2 {
        match index {
            0 => self.col0,
            1 => self.col1,
            _ => panic!("index out of bounds"),
        }
    }

    /// Returns row 0 of `self`.
    #[inline]
    #[must_use]
    pub const fn row0(self) -> Vec2 {
        Vec2::new(self.col0.x, self.col1.x)
    }

    /// Returns row 1 of `self`.
    #[inline]
    #[must_use]
    pub const fn row1(self) -> Vec2 {
        Vec2::new(self.col0.y, self.col1.y)
    }

    /// Returns all rows of `self`.
    #[inline]
    #[must_use]
    pub const fn rows(self) -> [Vec2; 2] {
        [self.row0(), self.row1()]
    }

    /// Returns row of `self` at `index`.
    ///
    /// `0` maps to `row0`, `1` maps to `row1`.
    ///
    /// # Panics
    /// Panics if `index` is greater than `1`.
    #[inline]
    #[must_use]
    pub const fn row(self, index: usize) -> Vec2 {
        match index {
            0 => self.row0(),
            1 => self.row1(),
            _ => panic!("index out of bounds"),
        }
    }

    /// Returns the transpose of `self`.
    #[inline]
    #[must_use]
    pub const fn transpose(self) -> Self {
        Self::from_cols(self.row0(), self.row1())
    }

    /// Returns the diagonal of `self`.
    #[inline]
    #[must_use]
    pub const fn diagonal(self) -> Vec2 {
        Vec2::new(self.col0.x, self.col1.y)
    }

    /// Returns the determinant of `self`.
    #[inline]
    #[must_use]
    pub const fn determinant(self) -> f32 {
        self.col0.x * self.col1.y - self.col0.y * self.col1.x
    }

    /// Returns the inverse of `self` or `None` if `self` is not invertible.
    ///
    /// # Edge cases
    /// If `self` is not invertible, returns `None`.
    #[must_use]
    pub const fn try_inverse(self) -> Option<Self> {
        let det: f32 = self.determinant();

        if det.abs() < Self::EPSILON {
            return None;
        }
        let det_recip: f32 = det.recip();

        Some(Self::from_cols(
            Vec2::new(self.col1.y * det_recip, -self.col0.y * det_recip),
            Vec2::new(-self.col1.x * det_recip, self.col0.x * det_recip),
        ))
    }

    /// Returns the inverse of `self`.
    ///
    /// # Panics
    /// Panics if `self` is not invertible.
    #[must_use]
    pub const fn inverse(self) -> Self {
        self.try_inverse().expect("matrix not invertible")
    }

    /// Returns `true` if `self` is invertible.
    ///
    /// Uses [`EPSILON`](Self::EPSILON) to determine whether the determinant
    /// is sufficiently far from zero.
    #[inline]
    #[must_use]
    pub const fn is_invertible(self) -> bool {
        self.determinant().abs() >= Self::EPSILON
    }

    /// Returns `v` transformed by `self`.
    #[inline]
    #[must_use]
    pub const fn transform(self, v: Vec2) -> Vec2 {
        Vec2::new(
            self.col0.x * v.x + self.col1.x * v.y,
            self.col0.y * v.x + self.col1.y * v.y,
        )
    }

    /// Returns `self` with the absolute value of each component.
    #[inline]
    #[must_use]
    pub const fn abs(self) -> Self {
        Self::from_cols(self.col0.abs(), self.col1.abs())
    }

    /// Returns `self` with the reciprocal value of each component.
    #[inline]
    #[must_use]
    pub const fn recip(self) -> Self {
        Self::from_cols(self.col0.recip(), self.col1.recip())
    }

    /// Returns `true` if `self` and `other` approximately represent the same matrix.
    ///
    /// Uses [`EPSILON`](Self::EPSILON) as the threshold.
    #[inline]
    #[must_use]
    pub const fn approx_eq(self, other: Self) -> bool {
        let a: [f32; 4] = self.to_array();
        let b: [f32; 4] = other.to_array();

        let mut i: usize = 0;
        while i < 4 {
            if (a[i] - b[i]).abs() >= Self::EPSILON {
                return false;
            }
            i += 1;
        }
        true
    }

    /// Returns `true` if all component of `self` are finite.
    #[inline]
    #[must_use]
    pub const fn is_finite(self) -> bool {
        let m: [f32; 4] = self.to_array();

        let mut i: usize = 0;
        while i < 4 {
            if !m[i].is_finite() {
                return false;
            }
            i += 1;
        }
        true
    }

    /// Returns `true` if any component of `self` is `NaN`.
    #[inline]
    #[must_use]
    pub const fn is_nan(self) -> bool {
        let m: [f32; 4] = self.to_array();

        let mut i: usize = 0;
        while i < 4 {
            if m[i].is_nan() {
                return true;
            }
            i += 1;
        }
        false
    }
}

impl Add for Mat2 {
    type Output = Self;

    /// Returns the component-wise sum of `self` and `rhs`.
    ///
    /// Equivalent to [`add`](Self::add()).
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        self.add(rhs)
    }
}

impl AddAssign for Mat2 {
    /// Sets self to the component-wise sum of `self` and `rhs`.
    ///
    /// Equivalent to [`add_assign`](Self::add_assign()).
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        self.add_assign(rhs);
    }
}

impl Sub for Mat2 {
    type Output = Self;

    /// Returns the component-wise difference of `self` and `rhs`.
    ///
    /// Equivalent to [`sub`](Self::sub()).
    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        self.sub(rhs)
    }
}

impl SubAssign for Mat2 {
    /// Sets self to the component-wise difference of `self` and `rhs`.
    ///
    /// Equivalent to [`sub_assign`](Self::sub_assign()).
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        self.sub_assign(rhs);
    }
}

impl Mul for Mat2 {
    type Output = Self;

    /// Returns the matrix product of `self` and `rhs`.
    ///
    /// Equivalent to [`mul`](Self::mul()).
    #[inline]
    fn mul(self, rhs: Self) -> Self::Output {
        self.mul(rhs)
    }
}

impl MulAssign for Mat2 {
    /// Sets `self` to the matrix product of `self` and `rhs`.
    ///
    /// Equivalent to [`mul_assign`](Self::mul_assign()).
    #[inline]
    fn mul_assign(&mut self, rhs: Self) {
        self.mul_assign(rhs);
    }
}

impl Mul<Vec2> for Mat2 {
    type Output = Vec2;

    /// Returns `rhs` transformed by `self`.
    ///
    /// Equivalent to [`mul_vec2`](Self::mul_vec2()).
    #[inline]
    fn mul(self, rhs: Vec2) -> Self::Output {
        self.mul_vec2(rhs)
    }
}

impl Mul<f32> for Mat2 {
    type Output = Self;

    /// Returns `self` with each component multiplied by `rhs`.
    ///
    /// Equivalent to [`mul_scalar`](Self::mul_scalar()).
    #[inline]
    fn mul(self, rhs: f32) -> Self::Output {
        self.mul_scalar(rhs)
    }
}

impl MulAssign<f32> for Mat2 {
    /// Sets `self` to `self` with each component multiplied by `rhs`.
    ///
    /// Equivalent to [`mul_assign_scalar`](Self::mul_assign_scalar()).
    #[inline]
    fn mul_assign(&mut self, rhs: f32) {
        self.mul_assign_scalar(rhs);
    }
}

impl Div<f32> for Mat2 {
    type Output = Self;

    /// Returns `self` with each component divided by `rhs`.
    ///
    /// Equivalent to [`div_scalar`](Self::div_scalar()).
    #[inline]
    fn div(self, rhs: f32) -> Self::Output {
        self.div_scalar(rhs)
    }
}

impl DivAssign<f32> for Mat2 {
    /// Sets `self` to `self` with each component divided by `rhs`.
    ///
    /// Equivalent to [`div_assign_scalar`](Self::div_assign_scalar()).
    #[inline]
    fn div_assign(&mut self, rhs: f32) {
        self.div_assign_scalar(rhs);
    }
}

impl Neg for Mat2 {
    type Output = Self;

    /// Returns `self` with negated components.
    ///
    /// Equivalent to [`neg`](Self::neg()).
    #[inline]
    fn neg(self) -> Self::Output {
        self.neg()
    }
}

impl AsRef<[f32; 4]> for Mat2 {
    /// Returns a reference to the components as `[col0, col1]`,
    /// with each column as `[x, y]`.
    ///
    /// Equivalent to [`as_ref`](Self::as_ref()).
    ///
    /// # Safety
    /// Relies on `Mat2` being `#[repr(C)]`
    ///
    /// # Convention
    /// Array is in column-major order.
    #[inline]
    fn as_ref(&self) -> &[f32; 4] {
        self.as_ref()
    }
}

impl AsMut<[f32; 4]> for Mat2 {
    /// Returns a mutable reference to the components as `[col0, col1]`,
    /// with each column as `[x, y]`.
    ///
    /// Equivalent to [`as_mut`](Self::as_mut()).
    ///
    /// # Safety
    /// Relies on `Mat2` being `#[repr(C)]`
    ///
    /// # Convention
    /// Array is in column-major order.
    #[inline]
    fn as_mut(&mut self) -> &mut [f32; 4] {
        self.as_mut()
    }
}

impl Display for Mat2 {
    /// Formats `self` as `[col0, col1]`,
    /// with each column as `(x, y)`.
    ///
    /// Respects the precision of `f`.
    #[inline]
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if let Some(p) = f.precision() {
            write!(f, "[{:.*}, {:.*}]", p, self.col0, p, self.col1,)
        } else {
            write!(f, "[{}, {}]", self.col0, self.col1)
        }
    }
}

impl Default for Mat2 {
    /// Default is [`IDENTITY`](Self::IDENTITY).
    #[inline]
    fn default() -> Self {
        Self::IDENTITY
    }
}

impl From<[f32; 4]> for Mat2 {
    /// Creates a `Mat2` from a `[f32; 4]`, in column-major order:
    /// `[col0.x, col0.y, col1.x, col1.y]`.
    ///
    /// Equivalent to [`from_array`](Self::from_array()).
    #[inline]
    fn from(v: [f32; 4]) -> Self {
        Self::from_array(v)
    }
}

impl From<Mat2> for [f32; 4] {
    /// Returns a copy of the components as `[col0, col1]`,
    /// with each column as `[x, y]`, in column-major order.
    ///
    /// Equivalent to [`to_array`](Mat2::to_array()).
    #[inline]
    fn from(v: Mat2) -> Self {
        v.to_array()
    }
}

impl From<Mat3> for Mat2 {
    /// Creates a `Mat2` from a `Mat3`, discarding `col2` and `row2`.
    ///
    /// Equivalent to [`from_mat3`](Self::from_mat3()).
    #[inline]
    fn from(v: Mat3) -> Self {
        Self::from_mat3(v)
    }
}

#[cfg(feature = "serde")]
impl serde::Serialize for Mat2 {
    /// Serializes as a `[f32; 4]` in column-major order.
    ///
    /// # Format
    /// Encoded as a flat 4-element array. Component order matches [`to_array`](Self::to_array()).
    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        self.to_array().serialize(serializer)
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Mat2 {
    /// Deserializes from a `[f32; 4]` in column-major order.
    ///
    /// # Format
    /// Decoded as a flat 4-element array. Component order matches [`from_array`](Self::from_array()).
    ///
    /// # Errors
    /// Returns an error if array is not exactly 4 elements.
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        <[f32; 4]>::deserialize(deserializer).map(Self::from_array)
    }
}

#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Zeroable for Mat2 {}

#[cfg(feature = "bytemuck")]
unsafe impl bytemuck::Pod for Mat2 {}
