//! Cube and axial hex coordinates.

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

use num_traits::{real::Real, AsPrimitive, One, Signed, Zero};

use super::directions::Direction;
use super::line::Line;

/// Cube hex coordinates representing points. Only valid points on the plane
/// can be normally created.
///
/// `T` should generally be a signed integer type. Floating point numbers have
/// precision problems that may lead to invalid coordinates after operations.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[repr(C)]
pub struct Point<T> {
    x: T,
    y: T,
    z: T,
    w: T,
}

/// Axial hex coordinates representing points. Always valid.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct PointAxial<T> {
    pub x: T,
    pub y: T,
    pub w: T,
}

/// Cube hex coordinates representing vectors. Only valid points on the plane
/// can be normally created.
///
/// `T` should generally be a signed integer type. Floating point numbers have
/// precision problems that may lead to invalid coordinates after operations.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[repr(C)]
pub struct Vector<T> {
    x: T,
    y: T,
    z: T,
    w: T,
}

/// Axial hex coordinates representing vectors. Always valid.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct VectorAxial<T> {
    pub x: T,
    pub y: T,
    pub w: T,
}

macro_rules! impl_hex_common {
    ($cube:ident, $axial:ident) => {
        impl<T> $cube<T> {
            pub fn x(&self) -> &T {
                &self.x
            }

            pub fn y(&self) -> &T {
                &self.y
            }

            pub fn z(&self) -> &T {
                &self.z
            }

            pub fn w(&self) -> &T {
                &self.w
            }

            pub fn into_axial(self) -> $axial<T> {
                self.into()
            }
        }

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

        impl<T: Copy + Add + Zero> $cube<T> {
            /// Create from coordinates. Returns `None` if invalid.
            pub fn try_from_coords(x: T, y: T, z: T, w: T) -> Option<Self> {
                if T::is_zero(&(x + y + z)) {
                    Some($cube { x, y, z, w })
                } else {
                    None
                }
            }

            /// Create from coordinates. Will panic if invalid.
            ///
            /// # Panics
            ///
            /// The function panics when called with coordinates that don't
            /// satisfy the hex plane invariant.
            pub fn new(x: T, y: T, z: T, w: T) -> Self {
                Self::try_from_coords(x, y, z, w).expect("invalid cube coords")
            }
        }

        impl<T: Zero> $cube<T> {
            /// Return a vector where all components are zero.
            pub fn zero() -> Self {
                $cube {
                    x: T::zero(),
                    y: T::zero(),
                    z: T::zero(),
                    w: T::zero(),
                }
            }
        }

        impl<T: Real> $cube<T> {
            /// Round in hex space, upholding plane invariant.
            pub fn round(self) -> Self {
                let x = self.x.round();
                let y = self.y.round();
                let z = self.z.round();
                let w = self.w.round();

                let x_diff = (x - self.x).abs();
                let y_diff = (y - self.y).abs();
                let z_diff = (z - self.z).abs();

                if x_diff > y_diff && x_diff > z_diff {
                    $cube {
                        x: T::zero() - y - z,
                        y,
                        z,
                        w,
                    }
                } else if y_diff > z_diff {
                    $cube {
                        x,
                        y: T::zero() - x - z,
                        z,
                        w,
                    }
                } else {
                    $cube {
                        x,
                        y,
                        z: T::zero() - x - y,
                        w,
                    }
                }
            }

            /// Round in hex space and cast to a primitive type.
            ///
            /// `N` must be a primitive numeric type.
            pub fn cast_round<N>(self) -> $cube<N>
            where
                N: 'static + Copy,
                T: AsPrimitive<N>,
            {
                let rounded = self.round();
                $cube {
                    x: rounded.x.as_(),
                    y: rounded.y.as_(),
                    z: rounded.z.as_(),
                    w: rounded.w.as_(),
                }
            }
        }

        impl<T> $cube<T> {
            /// Cast to a primitive type and fix the coordinate if necessary,
            /// by discarding the Z-axis. This is preferable for integral `T`s.
            ///
            /// `N` must be a primitive numeric type.
            pub fn cast_fix<N>(self) -> $cube<N>
            where
                N: 'static + Copy + Add + Zero + Sub<Output = N>,
                T: AsPrimitive<N>,
            {
                $axial::new(self.x.as_(), self.y.as_(), self.w.as_()).to_cube()
            }
        }

        impl<T> $axial<T> {
            pub fn new(x: T, y: T, w: T) -> Self {
                $axial { x, y, w }
            }
        }

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

        impl<T: Zero> $axial<T> {
            pub fn zero() -> Self {
                $axial {
                    x: T::zero(),
                    y: T::zero(),
                    w: T::zero(),
                }
            }
        }

        impl<T: One> $axial<T> {
            pub fn one() -> Self {
                $axial {
                    x: T::one(),
                    y: T::one(),
                    w: T::one(),
                }
            }
        }

        impl<T: Copy + Zero + Sub<Output = T>> $axial<T> {
            pub fn to_cube(self) -> $cube<T> {
                self.into()
            }
        }

        impl<T: Real> $axial<T> {
            pub fn round(self) -> Self {
                self.to_cube().round().into_axial()
            }
        }

        impl<T> $axial<T> {
            /// Cast to a primitive type.
            ///
            /// `N` must be a primitive numeric type.
            pub fn cast<N>(self) -> $axial<N>
            where
                N: 'static + Copy,
                T: AsPrimitive<N>,
            {
                $axial::new(self.x.as_(), self.y.as_(), self.w.as_())
            }
        }

        impl<T> From<$cube<T>> for $axial<T> {
            fn from(c: $cube<T>) -> Self {
                $axial {
                    x: c.x,
                    y: c.y,
                    w: c.w,
                }
            }
        }

        impl<T: Copy + Zero + Sub<Output = T>> From<$axial<T>> for $cube<T> {
            fn from(c: $axial<T>) -> $cube<T> {
                $cube {
                    x: c.x,
                    y: c.y,
                    z: T::zero() - c.x - c.y,
                    w: c.w,
                }
            }
        }
    };
}

impl_hex_common!(Point, PointAxial);
impl_hex_common!(Vector, VectorAxial);

impl<T> Point<T> {
    /// Convert this point to a vector with the same component values.
    pub fn into_vector(self) -> Vector<T> {
        Vector {
            x: self.x,
            y: self.y,
            z: self.z,
            w: self.w,
        }
    }
}

impl<T> PointAxial<T> {
    /// Convert this point to a vector with the same component values.
    pub fn into_vector(self) -> VectorAxial<T> {
        VectorAxial {
            x: self.x,
            y: self.y,
            w: self.w,
        }
    }
}

impl<T> Point<T>
where
    T: 'static + Copy + AsPrimitive<f32>,
    f32: AsPrimitive<T>,
{
    /// Creates a line iterator to `other`, including both endpoints.
    pub fn line_to(self, other: Point<T>) -> Line<T> {
        Line::new(self, other, false, false)
    }

    /// Creates a line iterator from `other`, including both endpoints.
    pub fn line_from(self, other: Point<T>) -> Line<T> {
        Line::new(other, self, false, false)
    }

    /// Creates a line iterator to `other`, excluding some endpoints.
    pub fn line_to_excluding(
        self,
        other: Point<T>,
        exclude_start: bool,
        exclude_end: bool,
    ) -> Line<T> {
        Line::new(self, other, exclude_start, exclude_end)
    }

    /// Creates a line iterator from `other`, excluding some endpoints.
    pub fn line_from_excluding(
        self,
        other: Point<T>,
        exclude_start: bool,
        exclude_end: bool,
    ) -> Line<T> {
        Line::new(other, self, exclude_start, exclude_end)
    }
}

impl<T: Copy + Signed + One + Add + Sub<Output = T> + Div> Point<T> {
    /// Returns the manhattan distance to another point in hex space.
    pub fn distance(&self, other: Point<T>) -> T {
        (other - *self).length()
    }
}

impl<T> Vector<T> {
    /// Convert this vector to a point with the same component values.
    pub fn into_point(self) -> Point<T> {
        Point {
            x: self.x,
            y: self.y,
            z: self.z,
            w: self.w,
        }
    }
}

impl<T> VectorAxial<T> {
    /// Convert this vector to a point with the same component values.
    pub fn into_point(self) -> PointAxial<T> {
        PointAxial {
            x: self.x,
            y: self.y,
            w: self.w,
        }
    }
}

/// Direction vectors.
///
/// Flat:
///
/// ```text
///        yz +Y
///        __
///    yx /  \ xz +X
/// -X zx \__/ xy
///     -Y zy
/// ```
///
/// Pointy:
///
/// ```text
///     yx  /\  yz  +Y
/// -X  zx |  | xz  +X
/// -Y  zy  \/  xy
/// ```
impl<T: Zero + One + Neg<Output = T>> Vector<T> {
    /// +Y -Z direction vector.
    pub fn yz() -> Vector<T> {
        Vector {
            x: T::zero(),
            y: T::one(),
            z: -T::one(),
            w: T::zero(),
        }
    }

    /// +X -Z direction vector.
    pub fn xz() -> Vector<T> {
        Vector {
            x: T::one(),
            y: T::zero(),
            z: -T::one(),
            w: T::zero(),
        }
    }

    /// +X -Y direction vector.
    pub fn xy() -> Vector<T> {
        Vector {
            x: T::one(),
            y: -T::one(),
            z: T::zero(),
            w: T::zero(),
        }
    }

    /// +Z -Y direction vector.
    pub fn zy() -> Vector<T> {
        Vector {
            x: T::zero(),
            y: -T::one(),
            z: T::one(),
            w: T::zero(),
        }
    }

    /// +Z -X direction vector.
    pub fn zx() -> Vector<T> {
        Vector {
            x: -T::one(),
            y: T::zero(),
            z: T::one(),
            w: T::zero(),
        }
    }

    /// +Y -X direction vector.
    pub fn yx() -> Vector<T> {
        Vector {
            x: -T::one(),
            y: T::one(),
            z: T::zero(),
            w: T::zero(),
        }
    }

    /// +W direction vector.
    pub fn up() -> Vector<T> {
        Vector {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
            w: T::one(),
        }
    }

    /// -W direction vector.
    pub fn down() -> Vector<T> {
        Vector {
            x: T::zero(),
            y: T::zero(),
            z: T::zero(),
            w: -T::one(),
        }
    }

    /// All direction vectors in sequential order.
    pub fn directions() -> [Vector<T>; 8] {
        [
            Self::yz(),
            Self::xz(),
            Self::xy(),
            Self::zy(),
            Self::zx(),
            Self::yx(),
            Self::up(),
            Self::down(),
        ]
    }

    /// Get directional vector from a Direction value.
    pub fn in_direction(d: Direction) -> Vector<T> {
        use self::Direction::{DOWN, UP, XY, XZ, YX, YZ, ZX, ZY};
        match d {
            YZ => Self::yz(),
            XZ => Self::xz(),
            XY => Self::xy(),
            ZY => Self::zy(),
            ZX => Self::zx(),
            YX => Self::yx(),
            UP => Self::up(),
            DOWN => Self::down(),
        }
    }
}

impl<T: Zero + One + Neg<Output = T> + Eq> Vector<T> {
    /// Return the `Direction` that represents `self`, if `self` is a vector
    /// of length 1. Returns `None` otherwise.
    pub fn try_into_direction(self) -> Option<Direction> {
        use std::convert::TryFrom;

        Self::directions()
            .iter()
            .position(|v| v == &self)
            .map(|idx| {
                Direction::try_from(idx as u8)
                    .expect("length of array returned by directions should be 8")
            })
    }
}

impl<T: Zero + One + Neg<Output = T>> VectorAxial<T> {
    /// Get directional vector from a Direction value.
    pub fn in_direction(d: Direction) -> VectorAxial<T> {
        Vector::in_direction(d).into_axial()
    }
}

impl<T: Copy + Signed + One + Add + Div> Vector<T> {
    /// Returns the manhattan length in hex space.
    pub fn length(&self) -> T {
        use num_traits::sign::abs;
        (abs(self.x) + abs(self.y) + abs(self.z)) / (T::one() + T::one()) + abs(self.w)
    }
}

macro_rules! impl_vector_add {
    ($type:ident, $delta_type:ident; $( $f:ident ),* ) => {
        impl<T, O> Add<$delta_type<T>> for $type<T>
        where
            T: Add<Output=O>,
        {
            type Output = $type<O>;
            fn add(self, rhs: $delta_type<T>) -> Self::Output {
                $type {
                    $(
                        $f: self.$f + rhs.$f,
                    )*
                }
            }
        }

        impl<T> AddAssign<$delta_type<T>> for $type<T>
        where
            T: AddAssign,
        {
            fn add_assign(&mut self, rhs: $delta_type<T>) {
                $(
                    self.$f += rhs.$f;
                )*
            }
        }
    }
}

impl_vector_add!(Point, Vector; x, y, z, w);
impl_vector_add!(Vector, Vector; x, y, z, w);
impl_vector_add!(PointAxial, VectorAxial; x, y, w);
impl_vector_add!(VectorAxial, VectorAxial; x, y, w);

macro_rules! impl_vector_sub {
    ($type:ident; $( $f:ident ),* ) => {
        impl<T, O> Sub<$type<T>> for $type<T>
        where
            T: Sub<Output=O>,
        {
            type Output = $type<O>;
            fn sub(self, rhs: $type<T>) -> Self::Output {
                $type {
                    $(
                        $f: self.$f - rhs.$f,
                    )*
                }
            }
        }

        impl<T> SubAssign<$type<T>> for $type<T>
        where
            T: SubAssign,
        {
            fn sub_assign(&mut self, rhs: $type<T>) {
                $(
                    self.$f -= rhs.$f;
                )*
            }
        }
    };
    ($type:ident, $delta_type:ident; $( $f:ident ),* ) => {
        impl<T, O> Sub<$type<T>> for $type<T>
        where
            T: Sub<Output=O>,
        {
            type Output = $delta_type<O>;
            fn sub(self, rhs: $type<T>) -> Self::Output {
                $delta_type {
                    $(
                        $f: self.$f - rhs.$f,
                    )*
                }
            }
        }

        impl<T, O> Sub<$delta_type<T>> for $type<T>
        where
            T: Sub<Output=O>,
        {
            type Output = $type<O>;
            fn sub(self, rhs: $delta_type<T>) -> Self::Output {
                $type {
                    $(
                        $f: self.$f - rhs.$f,
                    )*
                }
            }
        }

        impl<T> SubAssign<$delta_type<T>> for $type<T>
        where
            T: SubAssign,
        {
            fn sub_assign(&mut self, rhs: $delta_type<T>) {
                $(
                    self.$f -= rhs.$f;
                )*
            }
        }
    }
}

impl_vector_sub!(Point, Vector; x, y, z, w);
impl_vector_sub!(Vector; x, y, z, w);
impl_vector_sub!(PointAxial, VectorAxial; x, y, w);
impl_vector_sub!(VectorAxial; x, y, w);

macro_rules! impl_vector_mul {
    ($type:ident; $( $f:ident ),*) => {
        impl<T, O> Mul<T> for $type<T>
        where
            T: Copy + Mul<Output=O>,
        {
            type Output = $type<O>;
            fn mul(self, rhs: T) -> Self::Output {
                $type {
                    $(
                        $f: self.$f * rhs,
                    )*
                }
            }
        }

        impl<T> MulAssign<T> for $type<T>
        where
            T: Copy + MulAssign,
        {
            fn mul_assign(&mut self, rhs: T) {
                $(
                    self.$f *= rhs;
                )*
            }
        }
    }
}

impl_vector_mul!(Vector; x, y, z, w);
impl_vector_mul!(VectorAxial; x, y, w);

impl<T, O> Div<T> for VectorAxial<T>
where
    T: Copy + Div<Output = O>,
{
    type Output = VectorAxial<O>;

    fn div(self, rhs: T) -> Self::Output {
        VectorAxial {
            x: self.x / rhs,
            y: self.y / rhs,
            w: self.w / rhs,
        }
    }
}

impl<T> DivAssign<T> for VectorAxial<T>
where
    T: Copy + DivAssign,
{
    fn div_assign(&mut self, rhs: T) {
        self.x /= rhs;
        self.y /= rhs;
        self.w /= rhs;
    }
}

macro_rules! impl_add_sub_dir {
    ($type:ident, $vector_type:ident) => {
        impl<T> Add<Direction> for $type<T>
        where
            T: Zero + One + Add + Neg<Output = T>,
        {
            type Output = $type<T>;

            fn add(self, rhs: Direction) -> Self::Output {
                self + $vector_type::<T>::in_direction(rhs)
            }
        }

        impl<T> AddAssign<Direction> for $type<T>
        where
            T: Zero + One + AddAssign<T> + Neg<Output = T>,
        {
            fn add_assign(&mut self, rhs: Direction) {
                *self += $vector_type::<T>::in_direction(rhs);
            }
        }

        impl<T> Sub<Direction> for $type<T>
        where
            T: Zero + One + Sub<Output = T> + Neg<Output = T>,
        {
            type Output = $type<T>;

            fn sub(self, rhs: Direction) -> Self::Output {
                self - $vector_type::<T>::in_direction(rhs)
            }
        }

        impl<T> SubAssign<Direction> for $type<T>
        where
            T: Zero + One + SubAssign<T> + Neg<Output = T>,
        {
            fn sub_assign(&mut self, rhs: Direction) {
                *self -= $vector_type::<T>::in_direction(rhs);
            }
        }
    };
}

impl_add_sub_dir!(Point, Vector);
impl_add_sub_dir!(PointAxial, VectorAxial);
impl_add_sub_dir!(Vector, Vector);
impl_add_sub_dir!(VectorAxial, VectorAxial);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn can_ops() {
        let mut pt = Point::new(0, 0, 0, 0);
        pt += Vector::new(1, 2, -3, 4);
        assert_eq!(Point::new(1, 2, -3, 4), pt);
        assert_eq!(Point::new(2, 2, -4, 4), pt + Vector::new(1, 0, -1, 0));
        pt -= Vector::new(1, -3, 2, 1) - Vector::new(1, 1, -2, 1);
        assert_eq!(Point::new(1, 6, -7, 4), pt);
        assert_eq!(Point::new(2, 7, -9, 3), pt - Vector::new(-1, -1, 2, 1));
        pt += pt.into_vector();

        let mut vc = pt - Point::new(2, 10, -12, 6);
        assert_eq!(Vector::new(0, 2, -2, 2), vc);
        vc += Vector::new(1, -2, 1, -1);
        assert_eq!(Vector::new(1, 0, -1, 1), vc);
        assert_eq!(Vector::new(2, 2, -4, 5), vc + Vector::new(1, 2, -3, 4));
        vc -= Vector::new(1, 1, -2, -3);
        assert_eq!(Vector::new(0, -1, 1, 4), vc);
        assert_eq!(Vector::new(-2, -2, 4, 3), vc - Vector::new(2, 1, -3, 1));
        vc *= 2;
        assert_eq!(Vector::new(0, -2, 2, 8), vc);
    }

    #[test]
    fn can_check_validity() {
        assert!(Point::try_from_coords(1, 2, -3, 1).is_some());
        assert!(Point::try_from_coords(-3, 2, 1, -1).is_some());
        assert!(Point::try_from_coords(0, 2, -2, 0).is_some());
        assert!(Vector::try_from_coords(1, 2, -3, 1).is_some());
        assert!(Vector::try_from_coords(-3, 2, 1, -1).is_some());
        assert!(Vector::try_from_coords(0, 2, -2, 0).is_some());

        assert!(Point::try_from_coords(1, -2, -3, 1).is_none());
        assert!(Point::try_from_coords(-3, 2, -1, -1).is_none());
        assert!(Point::try_from_coords(0, 3, -2, 0).is_none());
        assert!(Vector::try_from_coords(2, 2, -3, 1).is_none());
        assert!(Vector::try_from_coords(-3, -2, 1, -1).is_none());
        assert!(Vector::try_from_coords(0, 2, -1, 0).is_none());
    }

    #[test]
    fn can_convert() {
        use std::fmt::Debug;
        fn test_identity<T>(cube: Point<T>)
        where
            T: Copy + Eq + Debug + Zero + Sub<Output = T> + AsPrimitive<i64>,
            i64: AsPrimitive<T>,
        {
            let axial: PointAxial<T> = cube.into_axial();
            assert_eq!(cube, axial.to_cube());
            let cast_axial: PointAxial<i64> = cube.cast_fix().into_axial();
            assert_eq!(cube.cast_fix(), cast_axial.to_cube());
        }

        fn test_vec_identity<T>(cube: Vector<T>)
        where
            T: Copy + Eq + Debug + Zero + Sub<Output = T> + AsPrimitive<i64>,
            i64: AsPrimitive<T>,
        {
            let axial: VectorAxial<T> = cube.into_axial();
            assert_eq!(cube, axial.to_cube());
            let cast_axial: VectorAxial<i64> = cube.cast_fix().into_axial();
            assert_eq!(cube.cast_fix(), cast_axial.to_cube());
        }

        test_identity(Point::new(1, 2, -3, 4));
        test_identity(Point::new(-1, 4, -3, 4));
        test_identity(Point::new(2, 1, -3, -2));
        test_identity(Point::new(-6, 7, -1, -2));

        test_vec_identity(Vector::new(1, 2, -3, 4));
        test_vec_identity(Vector::new(-1, 4, -3, 4));
        test_vec_identity(Vector::new(2, 1, -3, -2));
        test_vec_identity(Vector::new(-6, 7, -1, -2));
    }

    #[test]
    fn can_round() {
        assert_eq!(
            Point::new(1, 2, -3, 4),
            Point::new(1.4, 2.2, -3.6, 4.2).cast_round()
        );
        assert_eq!(
            Point::new(1, 2, -3, 4),
            Point::new(0.6, 2.0, -2.6, 3.6).cast_round()
        );
        assert_eq!(
            Point::new(1, 2, -3, 4),
            Point::new(0.6, 2.4, -3.0, 3.6).cast_round()
        );
        assert_eq!(
            Vector::new(1, 2, -3, 4),
            Vector::new(1.4, 2.2, -3.6, 4.2).cast_round()
        );
        assert_eq!(
            Vector::new(1, 2, -3, 4),
            Vector::new(0.6, 2.0, -2.6, 3.6).cast_round()
        );
        assert_eq!(
            Vector::new(1, 2, -3, 4),
            Vector::new(0.6, 2.4, -3.0, 3.6).cast_round()
        );
        assert_eq!(
            Vector::new(1, 0, -1, 0),
            Vector::new(0.4, 0.3, -0.7, 0.0).cast_round()
        );
        assert_eq!(
            PointAxial::new(1, 2, 4),
            PointAxial::new(1.4, 2.2, 4.2).round().cast()
        );
        assert_eq!(
            PointAxial::new(1, 2, 4),
            PointAxial::new(0.6, 2.0, 3.6).round().cast()
        );
        assert_eq!(
            PointAxial::new(1, 2, 4),
            PointAxial::new(0.6, 2.4, 3.6).round().cast()
        );
        assert_eq!(
            VectorAxial::new(1, 2, 4),
            VectorAxial::new(1.4, 2.2, 4.2).round().cast()
        );
        assert_eq!(
            VectorAxial::new(1, 2, 4),
            VectorAxial::new(0.6, 2.0, 3.6).round().cast()
        );
        assert_eq!(
            VectorAxial::new(1, 2, 4),
            VectorAxial::new(0.6, 2.4, 3.6).round().cast()
        );
    }

    #[test]
    fn can_calculate_length() {
        assert_eq!(3, Vector::new(1, 2, -3, 0).length());
        assert_eq!(5, Vector::new(1, -4, 3, -1).length());
        assert_eq!(8, Vector::new(7, -4, -3, 1).length());
        assert_eq!(1, Point::new(0, 0, 0, 0).distance(Point::new(1, -1, 0, 0)));
        assert_eq!(3, Point::new(1, 0, -1, 0).distance(Point::new(0, 2, -2, 1)));
        assert_eq!(
            5,
            Point::new(1, 0, -1, -1).distance(Point::new(2, 2, -4, -3))
        );
    }

    #[test]
    fn can_iter_directions() {
        let reference = {
            use self::Direction as D;
            vec![D::YZ, D::XZ, D::XY, D::ZY, D::ZX, D::YX, D::UP, D::DOWN]
        };

        assert_eq!(reference, Direction::iter().collect::<Vec<_>>());
    }

    #[test]
    fn can_neg_directions() {
        for dir in Direction::iter() {
            let sum = Vector::<i16>::in_direction(dir) + Vector::in_direction(-dir);
            assert_eq!(Vector::zero(), sum);
        }
    }

    #[test]
    fn can_get_direction_from_vector() {
        for dir in Direction::iter() {
            assert_eq!(
                Some(dir),
                Vector::<i16>::in_direction(dir).try_into_direction()
            );
        }
    }
}
