extern crate alga;
#[macro_use]
extern crate alga_derive;
extern crate approx;
extern crate quickcheck;

use alga::general::{AbstractMagma, Additive, Identity, Multiplicative, TwoSidedInverse, Field};

use approx::{AbsDiffEq, RelativeEq, UlpsEq};

use quickcheck::{Arbitrary, Gen};
use num_traits::{Zero, One};
use std::ops::{Add, AddAssign, Sub, SubAssign, Neg, Mul, MulAssign, Div, DivAssign};

#[derive(Alga, Clone, PartialEq, Debug)]
#[alga_traits(Field(Additive, Multiplicative))]
#[alga_quickcheck]
struct W(f64);

fn test_trait_impl() {
    fn is_field<T: Field>() {}
    is_field::<W>();
}

impl AbsDiffEq for W {
    type Epsilon = W;
    fn default_epsilon() -> W {
        W(0.0000000001)
    }

    fn abs_diff_eq(&self, other: &W, epsilon: W) -> bool {
        self.0.abs_diff_eq(&other.0, epsilon.0)
    }
}

impl RelativeEq for W {
    fn default_max_relative() -> W {
        W(0.0000000001)
    }

    fn relative_eq(&self, other: &Self, epsilon: W, max_relative: W) -> bool {
        self.0.relative_eq(&other.0, epsilon.0, max_relative.0)
    }
}

impl UlpsEq for W {
    fn default_max_ulps() -> u32 {
        40
    }

    fn ulps_eq(&self, other: &Self, epsilon: W, max_ulps: u32) -> bool {
        self.0.ulps_eq(&other.0, epsilon.0, max_ulps)
    }
}

impl Arbitrary for W {
    fn arbitrary<G: Gen>(g: &mut G) -> Self {
        W(f64::arbitrary(g))
    }
    fn shrink(&self) -> Box<Iterator<Item = Self>> {
        Box::new(self.0.shrink().map(W))
    }
}

impl AbstractMagma<Additive> for W {
    fn operate(&self, right: &Self) -> Self {
        W(self.0 + right.0)
    }
}
impl AbstractMagma<Multiplicative> for W {
    fn operate(&self, right: &Self) -> Self {
        W(self.0 * right.0)
    }
}

impl TwoSidedInverse<Additive> for W {
    fn two_sided_inverse(&self) -> Self {
        W(-self.0)
    }
}

impl TwoSidedInverse<Multiplicative> for W {
    fn two_sided_inverse(&self) -> Self {
        W(1. / self.0)
    }
}

impl Identity<Additive> for W {
    fn identity() -> Self {
        W(0.)
    }
}

impl Identity<Multiplicative> for W {
    fn identity() -> Self {
        W(1.)
    }
}

impl Add<W> for W {
    type Output = W;

    fn add(self, rhs: W) -> W {
        W(self.0 + rhs.0)
    }
}

impl Sub<W> for W {
    type Output = W;

    fn sub(self, rhs: W) -> W {
        W(self.0 - rhs.0)
    }
}

impl AddAssign<W> for W {
    fn add_assign(&mut self, rhs: W) {
        self.0 += rhs.0
    }
}

impl SubAssign<W> for W {
    fn sub_assign(&mut self, rhs: W) {
        self.0 -= rhs.0
    }
}

impl Neg for W {
    type Output = W;

    fn neg(self) -> W {
        W(-self.0)
    }
}

impl Zero for W {
    fn zero() -> W {
        W(0.0)
    }

    fn is_zero(&self) -> bool {
        self.0.is_zero()
    }
}

impl One for W {
    fn one() -> W {
        W(1.0)
    }
}

impl Mul<W> for W {
    type Output = W;

    fn mul(self, rhs: W) -> W {
        W(self.0 * rhs.0)
    }
}


impl Div<W> for W {
    type Output = W;

    fn div(self, rhs: W) -> W {
        W(self.0 / rhs.0)
    }
}

impl MulAssign<W> for W {
    fn mul_assign(&mut self, rhs: W) {
        self.0 *= rhs.0
    }
}


impl DivAssign<W> for W {
    fn div_assign(&mut self, rhs: W) {
        self.0 /= rhs.0
    }
}
