//! 3-dimensional vector math for game-engine development and graphics.
//!
//! Const directional vectors assume right-handed coordinate system:
//! - +X = right, +Y = up, +Z = forward.
//!
//! ## Equality
//!
//! [`PartialEq`] uses an epsilon threshold rather than exact float comparison.
//!
//! ## Example
//!
//! ```rust
//! # use roofy::math::vec3::{Vec3};
//! let player: Vec3 = Vec3::ZERO;
//! let target: Vec3 = Vec3::new(3.0, 0.0, 4.0);
//!
//! let direction: Vec3 = (target - player).normalized();
//! let speed: f32 = 10.0;
//! let velocity: Vec3 = direction * speed;
//!
//! assert_eq!(direction, Vec3::new(0.6, 0.0, 0.8));
//! assert_eq!(velocity, Vec3::new(6.0, 0.0, 8.0));
//! ```
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};

/// 3-dimensional vector with `f32` components.
/// Same memory layout as `[f32; 3]`, making it safe for GPU uploads.
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct Vec3 {
    pub x: f32,
    pub y: f32,
    pub z: f32,
}

impl Vec3 {
    /// Threshold for treating a value as zero.
    pub const EPSILON: f32 = 1e-6;
    /// `EPSILON` squared.
    pub const EPSILON_SQ: f32 = Self::EPSILON * Self::EPSILON;

    /// Shorthand for writing Vec3::new(0.0, 0.0, 0.0).
    pub const ZERO: Self = Self::new(0.0, 0.0, 0.0);
    /// Shorthand for writing Vec3::new(1.0, 1.0, 1.0).
    pub const ONE: Self = Self::new(1.0, 1.0, 1.0);

    /// Shorthand for writing Vec3::new(1.0, 0.0, 0.0).
    pub const RIGHT: Self = Self::new(1.0, 0.0, 0.0);
    /// Shorthand for writing Vec3::new(-1.0, 0.0, 0.0).
    pub const LEFT: Self = Self::new(-1.0, 0.0, 0.0);
    /// Shorthand for writing Vec3::new(0.0, 1.0, 0.0).
    pub const UP: Self = Self::new(0.0, 1.0, 0.0);
    /// Shorthand for writing Vec3::new(0.0, -1.0, 0.0).
    pub const DOWN: Self = Self::new(0.0, -1.0, 0.0);
    /// Shorthand for writing Vec3::new(0.0, 0.0, 1.0).
    pub const FORWARD: Self = Self::new(0.0, 0.0, 1.0);
    /// Shorthand for writing Vec3::new(0.0, 0.0, -1.0).
    pub const BACK: Self = Self::new(0.0, 0.0, -1.0);

    /// Creates a `Vec3` with `x`, `y` and `z` components.
    #[inline]
    pub const fn new(x: f32, y: f32, z: f32) -> Self {
        Self { x, y, z }
    }

    /// Creates a `Vec3` with all component `v`.
    #[inline]
    pub const fn splat(v: f32) -> Self {
        Self::new(v, v, v)
    }

    /// Returns length (magnitude) of `self`.
    #[inline]
    #[must_use]
    pub fn length(self) -> f32 {
        self.dot(self).sqrt()
    }

    /// Returns length (magnitude) squared of `self`.
    #[inline]
    #[must_use]
    pub const fn length_squared(self) -> f32 {
        self.dot(self)
    }

    /// Normalizes `self` to have a length of 1, while preserving direction.
    ///
    /// If `self` is near-zero, sets `self` to [`ZERO`](Self::ZERO).
    #[inline]
    pub fn normalize(&mut self) {
        let len_sq: f32 = self.length_squared();

        if len_sq < Self::EPSILON_SQ {
            *self = Vec3::ZERO;
            return;
        }

        *self *= 1.0 / len_sq.sqrt();
    }

    /// Returns a unit-length `Vec3` with direction of `self`.
    ///
    /// If `self` is near-zero, returns [`ZERO`](Self::ZERO).
    #[inline]
    #[must_use]
    pub fn normalized(self) -> Self {
        let len_sq: f32 = self.length_squared();

        if len_sq < Self::EPSILON_SQ {
            return Vec3::ZERO;
        }

        self * (1.0 / len_sq.sqrt())
    }

    /// Returns the dot product of `self` and `other`.
    #[inline]
    #[must_use]
    pub const fn dot(self, other: Self) -> f32 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    /// Returns the distance from `self` to `to`.
    #[inline]
    #[must_use]
    pub fn distance(self, to: Self) -> f32 {
        (self - to).length()
    }

    /// Returns the distance squared from `self` to `to`.
    #[inline]
    #[must_use]
    pub fn distance_squared(self, to: Self) -> f32 {
        (self - to).length_squared()
    }

    /// Returns the angle between `self` and `to` in radians.
    #[inline]
    #[must_use]
    pub fn angle(self, to: Self) -> f32 {
        let len_sq: f32 = self.length_squared() * to.length_squared();

        if len_sq < Self::EPSILON_SQ {
            return 0.0;
        }

        (self.dot(to) / len_sq.sqrt()).clamp(-1.0, 1.0).acos()
    }

    /// Returns the angle between `self` and `to` in degrees.
    #[inline]
    #[must_use]
    pub fn angle_degrees(self, to: Self) -> f32 {
        self.angle(to).to_degrees()
    }

    /// Returns the cross product of `self` and `other`.
    #[inline]
    #[must_use]
    pub const fn cross(self, other: Self) -> Self {
        Self::new(
            self.y * other.z - self.z * other.y,
            self.z * other.x - self.x * other.z,
            self.x * other.y - self.y * other.x,
        )
    }

    /// Returns `self` projected onto `normal`.
    #[inline]
    #[must_use]
    pub fn project(self, normal: Self) -> Self {
        let len_sq: f32 = normal.length_squared();

        if len_sq < Self::EPSILON_SQ {
            return Self::ZERO;
        }

        normal * (self.dot(normal) / len_sq)
    }

    /// Returns `self` reflected across `normal`.
    #[inline]
    #[must_use]
    pub fn reflect(self, normal: Self) -> Self {
        let factor: f32 = -2.0 * self.dot(normal);
        Self::new(
            factor * normal.x + self.x,
            factor * normal.y + self.y,
            factor * normal.z + self.z,
        )
    }

    /// Returns a `Vec3` with the smallest components of `self` and `other`.
    #[inline]
    #[must_use]
    pub const fn min(self, other: Self) -> Self {
        Self::new(
            self.x.min(other.x),
            self.y.min(other.y),
            self.z.min(other.z),
        )
    }

    /// Returns a `Vec3` with the largest components of `self` and `other`.
    #[inline]
    #[must_use]
    pub const fn max(self, other: Self) -> Self {
        Self::new(
            self.x.max(other.x),
            self.y.max(other.y),
            self.z.max(other.z),
        )
    }

    /// Returns a `Vec3` linearly interpolated between `self` and `to` with factor `t`.
    #[inline]
    #[must_use]
    pub fn lerp(self, to: Self, t: f32) -> Self {
        self + (to - self) * t
    }

    /// Returns a `Vec3` linearly interpolated between `self` and `to` with factor `t`.
    /// Clamps `t` between `0.0` and `1.0`.
    #[inline]
    #[must_use]
    pub fn lerp_clamped(self, to: Self, t: f32) -> Self {
        self + (to - self) * t.clamp(0.0, 1.0)
    }
}

impl Add for Vec3 {
    type Output = Self;

    /// Adds each component of `self` by corresponding components of `rhs`.
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
    }
}

impl AddAssign for Vec3 {
    /// Adds the corresponding components of `rhs` to `self`.
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        self.x += rhs.x;
        self.y += rhs.y;
        self.z += rhs.z;
    }
}

impl Sub for Vec3 {
    type Output = Self;

    /// Subtracts each component of `self` by corresponding components of `rhs`.
    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
    }
}

impl SubAssign for Vec3 {
    /// Subtracts the corresponding components of `rhs` from `self`.
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        self.x -= rhs.x;
        self.y -= rhs.y;
        self.z -= rhs.z;
    }
}

impl Mul for Vec3 {
    type Output = Self;

    /// Multiplies each component of `self` by corresponding components of `rhs`.
    #[inline]
    fn mul(self, rhs: Self) -> Self::Output {
        Self::new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z)
    }
}

impl MulAssign for Vec3 {
    /// Multiplies each component of `self` by the corresponding components of `rhs`.
    #[inline]
    fn mul_assign(&mut self, rhs: Self) {
        self.x *= rhs.x;
        self.y *= rhs.y;
        self.z *= rhs.z;
    }
}

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

    /// Multiplies each component of `self` by `rhs`.
    #[inline]
    fn mul(self, rhs: f32) -> Self::Output {
        Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
    }
}

impl MulAssign<f32> for Vec3 {
    /// Multiplies each component of `self` by `rhs`.
    #[inline]
    fn mul_assign(&mut self, rhs: f32) {
        self.x *= rhs;
        self.y *= rhs;
        self.z *= rhs;
    }
}

impl Div for Vec3 {
    type Output = Self;

    /// Divides each component of `self` by corresponding components of `rhs`.
    #[inline]
    fn div(self, rhs: Self) -> Self::Output {
        Self::new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z)
    }
}

impl DivAssign for Vec3 {
    /// Divides each component of `self` by the corresponding components of `rhs`.
    #[inline]
    fn div_assign(&mut self, rhs: Self) {
        self.x /= rhs.x;
        self.y /= rhs.y;
        self.z /= rhs.z;
    }
}

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

    /// Divides each component of `self` by `rhs`.
    #[inline]
    fn div(self, rhs: f32) -> Self::Output {
        Self::new(self.x / rhs, self.y / rhs, self.z / rhs)
    }
}

impl DivAssign<f32> for Vec3 {
    /// Divides each component of `self` by `rhs`.
    #[inline]
    fn div_assign(&mut self, rhs: f32) {
        self.x /= rhs;
        self.y /= rhs;
        self.z /= rhs;
    }
}

impl PartialEq for Vec3 {
    /// Returns `true` if distance squared between `self` and `other` is within [`EPSILON_SQ`](Self::EPSILON_SQ)
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        let x_diff: f32 = self.x - other.x;
        let y_diff: f32 = self.y - other.y;
        let z_diff: f32 = self.z - other.z;
        let len_sq: f32 = x_diff * x_diff + y_diff * y_diff + z_diff * z_diff;
        len_sq < Self::EPSILON_SQ
    }
}

impl Neg for Vec3 {
    type Output = Self;

    /// Negates all components of `self`.
    #[inline]
    fn neg(self) -> Self::Output {
        Self::new(-self.x, -self.y, -self.z)
    }
}

impl Default for Vec3 {
    /// Default is [`Zero`](Self::ZERO)
    #[inline]
    fn default() -> Self {
        Vec3::ZERO
    }
}
