use bevy::reflect::Reflect;
use core::{fmt, ops};

use super::VectorComponent;

#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
pub struct Vector3<T: VectorComponent> {
    pub x: T,
    pub y: T,
    pub z: T,
}

impl<T: VectorComponent> Vector3<T> {
    pub fn x() -> Self {
        Self {
            x: T::one(),
            y: T::zero(),
            z: T::zero(),
        }
    }
    pub fn y() -> Self {
        Self {
            x: T::zero(),
            y: T::one(),
            z: T::zero(),
        }
    }
    pub fn z() -> Self {
        Self {
            x: T::zero(),
            y: T::zero(),
            z: T::one(),
        }
    }
    pub fn zero() -> Self {
        Self {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
        }
    }
    pub fn one() -> Self {
        Self {
            x: T::one(),
            y: T::one(),
            z: T::one(),
        }
    }
    pub fn up() -> Self {
        Self {
            x: T::zero(),
            y: T::one(),
            z: T::zero(),
        }
    }
    pub fn down() -> Self {
        Self {
            x: T::zero(),
            y: -T::one(),
            z: T::zero(),
        }
    }
    pub fn left() -> Self {
        Self {
            x: -T::one(),
            y: T::zero(),
            z: T::zero(),
        }
    }
    pub fn right() -> Self {
        Self {
            x: T::one(),
            y: T::zero(),
            z: T::zero(),
        }
    }
    pub fn forward() -> Self {
        Self {
            x: T::zero(),
            y: T::zero(),
            z: -T::one(),
        }
    }
    pub fn back() -> Self {
        Self {
            x: T::zero(),
            y: T::zero(),
            z: T::one(),
        }
    }

    pub fn new(x: T, y: T, z: T) -> Self {
        Vector3 { x, y, z }
    }

    pub fn min(&self, other: &Self) -> Self {
        Vector3 {
            x: VectorComponent::min(self.x, other.x),
            y: VectorComponent::min(self.y, other.y),
            z: VectorComponent::min(self.z, other.z),
        }
    }

    pub fn max(&self, other: &Self) -> Self {
        Vector3 {
            x: VectorComponent::max(self.x, other.x),
            y: VectorComponent::max(self.y, other.y),
            z: VectorComponent::max(self.z, other.z),
        }
    }

    pub fn dot(&self, other: &Self) -> T {
        self.x * other.x + self.y * other.y + self.z * other.z
    }

    pub fn cross(&self, other: &Self) -> Self {
        Self {
            x: self.y * other.z - self.z * other.y,
            y: self.z * other.x - self.x * other.z,
            z: self.x * other.y - self.y * other.x,
        }
    }
}

impl<T: VectorComponent> fmt::Display for Vector3<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "({}, {}, {})", self.x, self.y, self.z)
    }
}

impl<T: VectorComponent> ops::Add<Vector3<T>> for Vector3<T> {
    type Output = Self;
    fn add(self, rhs: Self) -> Self::Output {
        Vector3 {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
            z: self.z + rhs.z,
        }
    }
}

impl<T: VectorComponent> ops::Neg for Vector3<T> {
    type Output = Self;
    fn neg(self) -> Self::Output {
        Vector3 {
            x: -self.x,
            y: -self.y,
            z: -self.z,
        }
    }
}

impl<T: VectorComponent> ops::Sub<Vector3<T>> for Vector3<T> {
    type Output = Self;
    fn sub(self, rhs: Self) -> Self::Output {
        self + (-rhs)
    }
}

impl<T: VectorComponent> ops::Mul<Vector3<T>> for Vector3<T> {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self::Output {
        Vector3 {
            x: self.x * rhs.x,
            y: self.y * rhs.y,
            z: self.z * rhs.z,
        }
    }
}

impl<T: VectorComponent> ops::Mul<T> for Vector3<T> {
    type Output = Self;
    fn mul(self, rhs: T) -> Self::Output {
        Vector3 {
            x: self.x * rhs,
            y: self.y * rhs,
            z: self.z * rhs,
        }
    }
}

impl<T: VectorComponent> ops::Div<Vector3<T>> for Vector3<T> {
    type Output = Self;
    fn div(self, rhs: Vector3<T>) -> Self::Output {
        Vector3 {
            x: self.x / rhs.x,
            y: self.y / rhs.y,
            z: self.z / rhs.z,
        }
    }
}

impl<T: VectorComponent> ops::Div<T> for Vector3<T> {
    type Output = Self;
    fn div(self, rhs: T) -> Self::Output {
        Vector3 {
            x: self.x / rhs,
            y: self.y / rhs,
            z: self.z / rhs,
        }
    }
}
