use crate::Vector;

use std::ops::{Add, AddAssign, Sub, SubAssign};

use scalars::Zero;

/// The 3D point type.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Point<T> {
    /// The x coordinate.
    pub x: T,
    /// The y coordinate.
    pub y: T,
    /// The z coordinate.
    pub z: T,
}

impl<T> Point<T> {
    /// Creates a new point from its coordinates.
    pub fn new(x: T, y: T, z: T) -> Self {
        Self { x, y, z }
    }
}

impl<T: Zero> Default for Point<T> {
    fn default() -> Self {
        Self {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
        }
    }
}

impl<T: Sub<Output = T>> Sub for Point<T> {
    type Output = Vector<T>;

    fn sub(self, other: Self) -> Vector<T> {
        Vector {
            x: self.x - other.x,
            y: self.y - other.y,
            z: self.z - other.z,
        }
    }
}

impl<T: Add<Output = T>> Add<Vector<T>> for Point<T> {
    type Output = Self;

    fn add(self, other: Vector<T>) -> Self {
        Self {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
        }
    }
}

impl<T: Sub<Output = T>> Sub<Vector<T>> for Point<T> {
    type Output = Self;

    fn sub(self, other: Vector<T>) -> Self {
        Self {
            x: self.x - other.x,
            y: self.y - other.y,
            z: self.z - other.z,
        }
    }
}

impl<T: AddAssign> AddAssign<Vector<T>> for Point<T> {
    fn add_assign(&mut self, other: Vector<T>) {
        self.x += other.x;
        self.y += other.y;
        self.z += other.z;
    }
}

impl<T: SubAssign> SubAssign<Vector<T>> for Point<T> {
    fn sub_assign(&mut self, other: Vector<T>) {
        self.x -= other.x;
        self.y -= other.y;
        self.z -= other.z;
    }
}

impl<T> From<[T; 3]> for Point<T> {
    fn from([x, y, z]: [T; 3]) -> Self {
        Self { x, y, z }
    }
}

impl<T> From<Point<T>> for [T; 3] {
    fn from(Point { x, y, z }: Point<T>) -> Self {
        [x, y, z]
    }
}
