use derive_more::{Add, AddAssign, Display, Neg, Sub, SubAssign};
use serde::{Deserialize, Serialize};
use std::ops::{Div, DivAssign, Mul, MulAssign};

#[derive(
    Debug,
    Copy,
    Clone,
    Add,
    AddAssign,
    Sub,
    SubAssign,
    PartialEq,
    Display,
    Neg,
    Serialize,
    Deserialize,
)]
#[display("({x}, {y}, {z})")]
pub struct Vector {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

impl Vector {
    pub const ZERO: Vector = Vector {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    pub const X: Vector = Vector {
        x: 1.0,
        y: 0.0,
        z: 0.0,
    };
    pub const Y: Vector = Vector {
        x: 0.0,
        y: 1.0,
        z: 0.0,
    };
    pub const Z: Vector = Vector {
        x: 0.0,
        y: 0.0,
        z: 1.0,
    };

    #[must_use]
    #[inline]
    pub const fn new(x: f64, y: f64, z: f64) -> Self {
        Self { x, y, z }
    }

    #[must_use]
    #[inline]
    pub const fn dot_product(self, rhs: Self) -> f64 {
        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
    }

    #[must_use]
    #[inline]
    pub const fn cross_product(self, rhs: Self) -> Self {
        Self {
            x: self.y * rhs.z - self.z * rhs.y,
            y: self.z * rhs.x - self.x * rhs.z,
            z: self.x * rhs.y - self.y * rhs.x,
        }
    }

    #[must_use]
    #[inline]
    pub const fn length2(self) -> f64 {
        self.dot_product(self)
    }

    #[must_use]
    #[inline]
    pub fn length(self) -> f64 {
        self.length2().sqrt()
    }

    #[must_use]
    #[inline]
    pub fn normalize(self) -> Self {
        self / self.length()
    }

    #[must_use]
    #[inline]
    pub fn with_length(self, length: f64) -> Self {
        self.normalize() * length
    }

    #[must_use]
    #[inline]
    pub const fn project_onto_vector(self, vector: Self) -> Self {
        let scale_factor = self.dot_product(vector) / vector.length2();
        // Manual multiplication to allow const-ification
        Self {
            x: vector.x * scale_factor,
            y: vector.y * scale_factor,
            z: vector.z * scale_factor,
        }
    }

    #[must_use]
    #[inline]
    pub fn project_onto_plane(self, vector: Self) -> Self {
        self - self.project_onto_vector(vector)
    }
}

impl Default for Vector {
    #[inline]
    fn default() -> Self {
        Self::ZERO
    }
}

impl<T> Mul<T> for Vector
where
    f64: Mul<T, Output = f64>,
    T: Copy,
{
    type Output = Self;

    #[inline]
    fn mul(self, rhs: T) -> Self::Output {
        Self::Output {
            x: self.x * rhs,
            y: self.y * rhs,
            z: self.z * rhs,
        }
    }
}

impl<T> MulAssign<T> for Vector
where
    f64: MulAssign<T>,
    T: Copy,
{
    #[inline]
    fn mul_assign(&mut self, rhs: T) {
        self.x *= rhs;
        self.y *= rhs;
        self.z *= rhs;
    }
}

impl Mul<Vector> for f64 {
    type Output = Vector;

    #[inline]
    fn mul(self, rhs: Vector) -> Self::Output {
        rhs * self
    }
}

impl<T> Div<T> for Vector
where
    f64: Div<T, Output = f64>,
    T: Copy,
{
    type Output = Self;

    #[inline]
    fn div(self, rhs: T) -> Self::Output {
        Self::Output {
            x: self.x / rhs,
            y: self.y / rhs,
            z: self.z / rhs,
        }
    }
}

impl<T> DivAssign<T> for Vector
where
    f64: DivAssign<T>,
    T: Copy,
{
    #[inline]
    fn div_assign(&mut self, rhs: T) {
        self.x /= rhs;
        self.y /= rhs;
        self.z /= rhs;
    }
}

impl Mul for Vector {
    type Output = f64;

    #[inline]
    fn mul(self, rhs: Self) -> f64 {
        self.dot_product(rhs)
    }
}
