use std::ops::{Add, Div, Mul, Neg, Sub};

/* Numeric */

pub trait Numeric:
    Sized
    + Neg<Output = Self>
    + Add<Self, Output = Self>
    + Sub<Self, Output = Self>
    + Mul<Self, Output = Self>
    + Div<Self, Output = Self>
    + Copy
{
    fn one() -> Self;
    fn zero() -> Self;
    fn abs(self) -> Self;
    fn sqrt(self) -> Self;
    fn is_zero(&self) -> bool;
    fn to_degrees(self) -> Self;
    fn to_radians(self) -> Self;
}

pub trait Float: Numeric {
    fn sin(self) -> Self;
    fn cos(self) -> Self;
    fn tan(self) -> Self;
    fn asin(self) -> Self;
    fn acos(self) -> Self;
    fn atan(self) -> Self;
    fn atan2(a: Self, b: Self) -> Self;
}

impl Numeric for gl::GLfloat {
    #[inline]
    fn one() -> Self {
        1.0
    }

    #[inline]
    fn zero() -> Self {
        0.0
    }

    #[inline]
    fn abs(self) -> Self {
        self.abs()
    }

    #[inline]
    fn sqrt(self) -> Self {
        f32::sqrt(self)
    }

    #[inline]
    fn is_zero(&self) -> bool {
        PartialEq::<f32>::eq(self, &0.0)
    }

    #[inline]
    fn to_degrees(self) -> Self {
        f32::to_degrees(self)
    }

    #[inline]
    fn to_radians(self) -> Self {
        f32::to_radians(self)
    }
}

impl Float for gl::GLfloat {
    #[inline]
    fn sin(self) -> Self {
        f32::sin(self)
    }

    #[inline]
    fn cos(self) -> Self {
        f32::cos(self)
    }

    #[inline]
    fn tan(self) -> Self {
        f32::tan(self)
    }

    #[inline]
    fn asin(self) -> Self {
        f32::acos(self)
    }

    #[inline]
    fn acos(self) -> Self {
        f32::acos(self)
    }

    #[inline]
    fn atan(self) -> Self {
        f32::atan(self)
    }

    #[inline]
    fn atan2(a: Self, b: Self) -> Self {
        f32::atan2(a, b)
    }
}

impl Numeric for gl::GLdouble {
    #[inline]
    fn one() -> Self {
        1.0
    }

    #[inline]
    fn zero() -> Self {
        0.0
    }

    #[inline]
    fn abs(self) -> Self {
        self.abs()
    }

    #[inline]
    fn sqrt(self) -> Self {
        f64::sqrt(self)
    }

    #[inline]
    fn is_zero(&self) -> bool {
        PartialEq::<f64>::eq(self, &0.0)
    }

    #[inline]
    fn to_degrees(self) -> Self {
        f64::to_degrees(self)
    }

    #[inline]
    fn to_radians(self) -> Self {
        f64::to_radians(self)
    }
}

impl Float for gl::GLdouble {
    #[inline]
    fn sin(self) -> Self {
        f64::sin(self)
    }

    #[inline]
    fn cos(self) -> Self {
        f64::cos(self)
    }

    #[inline]
    fn tan(self) -> Self {
        f64::tan(self)
    }

    #[inline]
    fn asin(self) -> Self {
        f64::acos(self)
    }

    #[inline]
    fn acos(self) -> Self {
        f64::acos(self)
    }

    #[inline]
    fn atan(self) -> Self {
        f64::atan(self)
    }

    #[inline]
    fn atan2(a: Self, b: Self) -> Self {
        f64::atan2(a, b)
    }
}

impl Numeric for gl::GLint {
    #[inline]
    fn one() -> Self {
        1
    }

    #[inline]
    fn zero() -> Self {
        0
    }

    #[inline]
    fn abs(self) -> Self {
        self.abs()
    }

    #[inline]
    fn sqrt(self) -> Self {
        f64::sqrt(self as f64) as i32
    }

    #[inline]
    fn is_zero(&self) -> bool {
        PartialEq::<i32>::eq(self, &0)
    }

    #[inline]
    fn to_degrees(self) -> Self {
        f32::to_degrees(self as f32) as i32
    }

    #[inline]
    fn to_radians(self) -> Self {
        f32::to_radians(self as f32) as i32
    }
}
