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

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Default)]
pub struct Vec2d<T> {
    x: T,
    y: T,
}

macro_rules! impl_vec_ops_for {
    ($($t:ty)*) => {$(
    impl Vec2d<$t>{
        /// Creates a new unit vector in a specific direction
        #[inline]
        pub fn unit_vector(direction: $t) -> Self {
            let (y, x) = direction.sin_cos();
            Vec2d { x, y }
        }

        /// Normalises the vector
        #[inline]
        pub fn normalise(self) -> Self {
            let magnitude = self.magnitude();
            if magnitude > 0.0 {
                return self * (1.0 / magnitude)
            } self
        }

        /// Returns the magnitude/length of the vector
        #[inline]
        pub fn magnitude(&self) -> $t {
            (self.x.powf(2.0) + self.y.powf(2.0)).sqrt()
        }

        /// Returns the (non-sqrt) magnitude/length of the vector
        #[inline]
        pub fn square_magnitude(&self) -> $t {
            self.x.powf(2.0) + self.y.powf(2.0)
        }

        /// Returns direction the vector is pointing
        #[inline]
        pub fn direction(&self) -> $t {
            self.y.atan2(self.x)
        }

        #[inline]
        pub fn clear(&mut self) {
            self.x = 0.0;
            self.y = 0.0;
        }
    }
    )*};
}
impl_vec_ops_for!(f32 f64);

/******************************************
 Standard Vector operators
******************************************/

impl<T> Vec2d<T> {
    #[inline]
    pub fn new(x: T, y: T) -> Vec2d<T>
    where
        T: Add + Mul + Div + Sub + Copy, {
        Vec2d { x, y }
    }

    #[inline]
    pub fn x(&self) -> T
    where
        T: Add + Mul + Div + Sub + Copy, {
        self.x
    }

    #[inline]
    pub fn set_x(&mut self, x: T)
    where
        T: Add + Mul + Div + Sub + Copy, {
        self.x = x
    }

    #[inline]
    pub fn y(&self) -> T
    where
        T: Add + Mul + Div + Sub + Copy, {
        self.y
    }

    #[inline]
    pub fn set_y(&mut self, y: T)
    where
        T: Add + Mul + Div + Sub + Copy, {
        self.y = y
    }
}

/// Add scalar to `Vec2d`
impl<T: Add + Copy> Add<T> for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn add(self, rhs: T) -> Self::Output {
        Vec2d {
            x: self.x + rhs,
            y: self.y + rhs,
        }
    }
}

/// Add two `Vec2d` together
impl<T: Add> Add for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        Vec2d {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

/// Add scalar to this `Vec2d`
impl<T: AddAssign + Copy> AddAssign<T> for Vec2d<T> {
    #[inline]
    fn add_assign(&mut self, rhs: T) {
        self.x += rhs;
        self.y += rhs;
    }
}

/// Add `Vec2d` to this `Vec2d`
impl<T: AddAssign> AddAssign<Vec2d<T>> for Vec2d<T> {
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        self.x += rhs.x;
        self.y += rhs.y;
    }
}

/// Subtract a scalar from this `Vec2d`
impl<T: Sub + Copy> Sub<T> for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn sub(self, rhs: T) -> Self::Output {
        Vec2d {
            x: self.x - rhs,
            y: self.y - rhs,
        }
    }
}

/// Subtract a `Vec2d` from this `Vec2d`
impl<T: Sub> Sub for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        Vec2d {
            x: self.x - rhs.x,
            y: self.y - rhs.y,
        }
    }
}

/// Subtract scalar from this `Vec2d`
impl<T: SubAssign + Copy> SubAssign<T> for Vec2d<T> {
    #[inline]
    fn sub_assign(&mut self, rhs: T) {
        self.x -= rhs;
        self.y -= rhs;
    }
}

/// Subtract `Vec2d` from this `Vec2d`
impl<T: SubAssign> SubAssign<Vec2d<T>> for Vec2d<T> {
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        self.x -= rhs.x;
        self.y -= rhs.y;
    }
}

/// Multiply `Vec2d` by scalar
impl<T: Mul + Copy> Mul<T> for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn mul(self, rhs: T) -> Self::Output {
        Vec2d {
            x: self.x * rhs,
            y: self.y * rhs,
        }
    }
}

/// Calculate and return the scalar product of this `Vec2d` by `Vec2d`
impl<T: Mul + Add> Mul for Vec2d<T>
where
    <T as Mul>::Output: Add,
{
    type Output = <<T as Mul>::Output as Add>::Output;
    #[inline]
    fn mul(self, rhs: Self) -> Self::Output {
        self.x * rhs.x + self.y * rhs.y
    }
}

/// Multiply this `Vec2d` by scalar
impl<T: MulAssign + Copy> MulAssign<T> for Vec2d<T> {
    #[inline]
    fn mul_assign(&mut self, rhs: T) {
        self.x *= rhs;
        self.y *= rhs;
    }
}

/// Multiply this `Vec2d` by a `Vec2d`
impl<T: MulAssign> MulAssign<Vec2d<T>> for Vec2d<T> {
    #[inline]
    fn mul_assign(&mut self, rhs: Self) {
        self.x *= rhs.x;
        self.y *= rhs.y;
    }
}

/// Divide `Vec2d` by scalar
impl<T: Div + Copy> Div<T> for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn div(self, rhs: T) -> Self::Output {
        Vec2d {
            x: self.x / rhs,
            y: self.y / rhs,
        }
    }
}

/// Divide `Vec2d` by `Vec2d`
impl<T: Div> Div for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn div(self, rhs: Self) -> Self::Output {
        Vec2d {
            x: self.x / rhs.x,
            y: self.y / rhs.y,
        }
    }
}

/// Divide this `Vec2d` by scalar
impl<T: DivAssign + Copy> DivAssign<T> for Vec2d<T> {
    #[inline]
    fn div_assign(&mut self, rhs: T) {
        self.x /= rhs;
        self.y /= rhs;
    }
}

/// Divide this `Vec2d` by a `Vec2d`
impl<T: DivAssign> DivAssign<Vec2d<T>> for Vec2d<T> {
    #[inline]
    fn div_assign(&mut self, rhs: Self) {
        self.x /= rhs.x;
        self.y /= rhs.y;
    }
}

// The following macros are ripped straight from core rust lib
//
// implements binary operators "&T op U", "T op &U", "&T op &U"
// based on "T op U" where T and U are expected to be `Copy`able
macro_rules! forward_ref_binop {
    (impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
        forward_ref_binop!(impl $imp, $method for $t, $u,
                #[stable(feature = "rust1", since = "1.0.0")]);
    };
    (impl $imp:ident, $method:ident for $t:ty, $u:ty, #[$attr:meta]) => {
        impl<'a> $imp<$u> for &'a $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: $u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, other)
            }
        }

        impl $imp<&$u> for $t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &$u) -> <$t as $imp<$u>>::Output {
                $imp::$method(self, *other)
            }
        }

        impl $imp<&$u> for &$t {
            type Output = <$t as $imp<$u>>::Output;

            #[inline]
            fn $method(self, other: &$u) -> <$t as $imp<$u>>::Output {
                $imp::$method(*self, *other)
            }
        }
    }
}

// implements "T op= &U", based on "T op= U"
// where U is expected to be `Copy`able
macro_rules! forward_ref_op_assign {
    (impl $imp:ident, $method:ident for $t:ty, $u:ty) => {
        forward_ref_op_assign!(impl $imp, $method for $t, $u,
            #[stable(feature = "op_assign_builtins_by_ref", since = "1.22.0")]);
    };
    (impl $imp:ident, $method:ident for $t:ty, $u:ty, #[$attr:meta]) => {
        impl $imp<&$u> for $t {
            #[inline]
            fn $method(&mut self, other: &$u) {
                $imp::$method(self, *other);
            }
        }
    }
}

macro_rules! vec2d_impl {
    ($($t:ty)*) => ($(
        forward_ref_binop! { impl Add, add for Vec2d<$t>, Vec2d<$t> }
        forward_ref_binop! { impl Add, add for Vec2d<$t>, $t }
        forward_ref_binop! { impl Sub, sub for Vec2d<$t>, Vec2d<$t> }
        forward_ref_binop! { impl Sub, sub for Vec2d<$t>, $t }
        forward_ref_binop! { impl Mul, mul for Vec2d<$t>, Vec2d<$t> }
        forward_ref_binop! { impl Mul, mul for Vec2d<$t>, $t }
        forward_ref_binop! { impl Div, div for Vec2d<$t>, Vec2d<$t> }
        forward_ref_binop! { impl Div, div for Vec2d<$t>, $t }

        forward_ref_op_assign! { impl AddAssign, add_assign for Vec2d<$t>, Vec2d<$t> }
        forward_ref_op_assign! { impl AddAssign, add_assign for Vec2d<$t>, $t }
        forward_ref_op_assign! { impl SubAssign, sub_assign for Vec2d<$t>, Vec2d<$t> }
        forward_ref_op_assign! { impl SubAssign, sub_assign for Vec2d<$t>, $t }
        forward_ref_op_assign! { impl MulAssign, mul_assign for Vec2d<$t>, Vec2d<$t> }
        forward_ref_op_assign! { impl MulAssign, mul_assign for Vec2d<$t>, $t }
        forward_ref_op_assign! { impl DivAssign, div_assign for Vec2d<$t>, Vec2d<$t> }
        forward_ref_op_assign! { impl DivAssign, div_assign for Vec2d<$t>, $t }
    )*)
}
vec2d_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 }

/// Invert the sign of a `Vec2d`
impl<T: Neg> Neg for Vec2d<T> {
    type Output = Vec2d<T::Output>;
    #[inline]
    fn neg(self) -> Self::Output {
        Vec2d {
            x: -self.x,
            y: -self.y,
        }
    }
}

/// Return a slice for this `Vec2d`
impl<T> Into<[T; 2]> for Vec2d<T> {
    #[inline]
    fn into(self) -> [T; 2] {
        [self.x, self.y]
    }
}

/// Convert a slice to a `Vec2d`
impl<T: Copy> From<[T; 2]> for Vec2d<T> {
    #[inline]
    fn from(array: [T; 2]) -> Self {
        Vec2d {
            x: array[0],
            y: array[1],
        }
    }
}

/// Return a tuple for this `Vec2d`
impl<T> Into<(T, T)> for Vec2d<T> {
    #[inline]
    fn into(self) -> (T, T) {
        (self.x, self.y)
    }
}

/// Convert a tuple to a `Vec2d`
impl<T> From<(T, T)> for Vec2d<T> {
    #[inline]
    fn from(tuple: (T, T)) -> Self {
        Vec2d {
            x: tuple.0,
            y: tuple.1,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::f32::consts::PI;
    use std::f64::consts::PI as F64_PI;

    #[allow(clippy::op_ref)]
    #[test]
    fn multiply_two_vec2d_for_scalar_f32() {
        let epsilon = 0.000001;
        let point1: Vec2d<f32> = Vec2d::new(1.0, 2.0);
        let point2: Vec2d<f32> = Vec2d::new(2.0, 3.0);
        assert!(point1 * point2 >= 8.0 && point1 * point2 < 8.0 + epsilon);
        assert!(&point1 * point2 >= 8.0 && &point1 * point2 < 8.0 + epsilon);
        assert!(point1 * &point2 >= 8.0 && point1 * &point2 < 8.0 + epsilon);
        assert!(&point1 * &point2 >= 8.0 && &point1 * &point2 < 8.0 + epsilon);
    }

    #[allow(clippy::op_ref)]
    #[test]
    fn multiply_vec2d_by_scalar_f32() {
        let point1: Vec2d<f32> = Vec2d::new(1.0, 2.0);
        let scalar: f32 = 3.0;
        let res = Vec2d::new(3.0, 6.0);
        assert_eq!(point1 * scalar, res);
        assert_eq!(&point1 * scalar, res);
        assert_eq!(point1 * &scalar, res);
        assert_eq!(&point1 * &scalar, res);
    }

    #[allow(clippy::op_ref)]
    #[test]
    fn multiply_two_vec2d_for_scalar_f64() {
        let epsilon = 0.000000000000001;
        let point1: Vec2d<f64> = Vec2d::new(1.0, 2.0);
        let point2: Vec2d<f64> = Vec2d::new(2.0, 3.0);
        assert!(point1 * point2 >= 8.0 && point1 * point2 < 8.0 + epsilon);
        assert!(&point1 * point2 >= 8.0 && &point1 * point2 < 8.0 + epsilon);
        assert!(point1 * &point2 >= 8.0 && point1 * &point2 < 8.0 + epsilon);
        assert!(&point1 * &point2 >= 8.0 && &point1 * &point2 < 8.0 + epsilon);
    }

    #[allow(clippy::op_ref)]
    #[test]
    fn multiply_vec2d_by_scalar_f64() {
        let point1: Vec2d<f64> = Vec2d::new(1.0, 2.0);
        let scalar: f64 = 3.0;
        let res = Vec2d::new(3.0, 6.0);
        assert_eq!(point1 * scalar, res);
        assert_eq!(&point1 * scalar, res);
        assert_eq!(point1 * &scalar, res);
        assert_eq!(&point1 * &scalar, res);
    }

    #[test]
    fn unit_vector_32() {
        let unit: Vec2d<f32> = Vec2d::<f32>::unit_vector(PI);
        assert_eq!(unit, Vec2d::new(-1.0, -0.00000008742278));
    }

    #[test]
    fn unit_vector_64() {
        let unit: Vec2d<f64> = Vec2d::<f64>::unit_vector(F64_PI);
        assert_eq!(unit, Vec2d::new(-1.0, 0.00000000000000012246467991473532));
    }

    #[test]
    fn normalise_32() {
        let point: Vec2d<f32> = Vec2d::new(4.0, 3.0);
        assert_eq!(point.normalise(), Vec2d::new(0.8, 0.6));
    }

    #[test]
    fn normalise_64() {
        let point: Vec2d<f64> = Vec2d::new(4.0, 3.0);
        assert_eq!(point.normalise(), Vec2d::new(0.8, 0.6000000000000001));
    }

    #[test]
    fn magnitude_32() {
        let point: Vec2d<f32> = Vec2d::new(4.0, 3.0);
        assert!(point.magnitude() >= 5.0 && point.magnitude() < 5.000001);
    }

    #[test]
    fn magnitude_64() {
        let point: Vec2d<f64> = Vec2d::new(4.0, 3.0);
        assert!(
            point.magnitude() >= 5.0 && point.magnitude() < 5.000000000000001
        );
    }

    #[test]
    fn direction_pi_32() {
        let point: Vec2d<f32> = Vec2d::new(-1.0, 0.0);
        assert!(
            point.direction() >= PI && point.direction() < PI + f32::EPSILON
        );
    }

    #[test]
    fn direction_pi_64() {
        let point: Vec2d<f64> = Vec2d::new(-1.0, 0.0);
        assert!(
            point.direction() >= F64_PI
                && point.direction() < F64_PI + f32::EPSILON as f64 // error is too large
        );
    }

    #[test]
    fn direction_pi2_32() {
        let point: Vec2d<f32> = Vec2d::new(0.0, 1.0);
        assert!(
            point.direction() >= PI / 2.0
                && point.direction() < PI / 2.0 + f32::EPSILON
        );
    }

    #[test]
    fn direction_pi2_64() {
        let point: Vec2d<f64> = Vec2d::new(0.0, 1.0);
        assert!(
            point.direction() >= F64_PI / 2.0
                && point.direction() < F64_PI / 2.0 + f64::EPSILON
        );
    }
}