use crate::algebra::helpers::exp::Exponentiation;

pub trait TrigOps: Exponentiation + Sized {
    const PI: Self;
    const TAU: Self;
    const FRAC_PI_2: Self;
    const FRAC_PI_3: Self;
    const FRAC_PI_4: Self;
    const FRAC_PI_6: Self;
    const FRAC_PI_8: Self;
    const FRAC_1_PI: Self;
    const FRAC_2_PI: Self;
    const FRAC_2_SQRT_PI: Self;

    fn sin(self) -> Self;
    fn cos(self) -> Self;
    fn tan(self) -> Self {
        self.try_tan().unwrap()
    }
    fn sin_cos(self) -> (Self, Self);

    fn try_tan(self) -> Option<Self>;

    fn asin(self) -> Self {
        self.try_asin().unwrap()
    }
    fn acos(self) -> Self {
        self.try_acos().unwrap()
    }
    fn atan(self) -> Self {
        self.try_atan().unwrap()
    }
    fn atan2(self, rhs: Self) -> Self {
        self.try_atan2(rhs).unwrap()
    }

    fn try_asin(self) -> Option<Self>;
    fn try_acos(self) -> Option<Self>;
    fn try_atan(self) -> Option<Self>;
    fn try_atan2(self, rhs: Self) -> Option<Self>;

    fn sinh(self) -> Self;
    fn cosh(self) -> Self;
    fn tanh(self) -> Self;

    fn coth(self) -> Self {
        self.try_coth().unwrap()
    }
    fn csch(self) -> Self {
        self.try_csch().unwrap()
    }

    fn try_coth(self) -> Option<Self>;
    fn try_csch(self) -> Option<Self>;

    fn asinh(self) -> Self;
    fn acosh(self) -> Self {
        self.try_acosh().unwrap()
    }
    fn atanh(self) -> Self {
        self.try_atanh().unwrap()
    }

    fn try_acosh(self) -> Option<Self>;
    fn try_atanh(self) -> Option<Self>;

    fn to_degrees(self) -> Self;
    fn to_radians(self) -> Self;
}

macro_rules! float_to_option {
    ($expr:expr) => {{
        let result = $expr;
        if result.is_infinite() || result.is_nan() {
            None
        } else {
            Some(result)
        }
    }};
}

macro_rules! impl_trig_float {
    ($($set:ty => [$($name: ident = $val:expr);* $(;)?]);* $(;)?) => {
        $(
                impl TrigOps for $set {

                $(
                    const $name: $set = $val;
                )*

                #[inline]
                fn try_tan(self) -> Option<Self> {
                    float_to_option!(self.tan())
                }
                #[inline]
                fn try_asin(self) -> Option<Self> {
                    float_to_option!(self.asin())
                }
                #[inline]
                fn try_acos(self) -> Option<Self> {
                    float_to_option!(self.acos())
                }
                #[inline]
                fn try_atan(self) -> Option<Self> {
                    float_to_option!(self.atan())
                }
                #[inline]
                fn try_atan2(self, rhs: Self) -> Option<Self> {
                    float_to_option!(self.atan2(rhs))
                }
                #[inline]
                fn try_acosh(self) -> Option<Self> {
                    float_to_option!(self.acosh())
                }
                #[inline]
                fn try_atanh(self) -> Option<Self> {
                    float_to_option!(self.atanh())
                }
                #[inline]
                fn try_coth(self) -> Option<Self> {
                    unimplemented!()
                }
                #[inline]
                fn try_csch(self) -> Option<Self> {
                    unimplemented!()
                }
                forward! {
                    fn sin(self) -> Self;
                    fn cos(self) -> Self;
                    fn tan(self) -> Self;
                    fn sin_cos(self) -> (Self, Self);

                    fn asin(self) -> Self;
                    fn acos(self) -> Self;
                    fn atan(self) -> Self;
                    fn atan2(self, rhs: Self) -> Self;

                    fn sinh(self) -> Self;
                    fn cosh(self) -> Self;
                    fn tanh(self) -> Self;

                    fn asinh(self) -> Self;
                    fn acosh(self) -> Self;
                    fn atanh(self) -> Self;

                    fn to_degrees(self) -> Self;
                    fn to_radians(self) -> Self;
                }
            }
        )*
    }
}

impl_trig_float! {
    f32 => [
        PI              = core::f32::consts::PI;
        TAU             = core::f32::consts::TAU;
        FRAC_PI_2       = core::f32::consts::FRAC_PI_2;
        FRAC_PI_3       = core::f32::consts::FRAC_PI_3;
        FRAC_PI_4       = core::f32::consts::FRAC_PI_4;
        FRAC_PI_6       = core::f32::consts::FRAC_PI_6;
        FRAC_PI_8       = core::f32::consts::FRAC_PI_8;
        FRAC_1_PI       = core::f32::consts::FRAC_1_PI;
        FRAC_2_PI       = core::f32::consts::FRAC_2_PI;
        FRAC_2_SQRT_PI  = core::f32::consts::FRAC_2_SQRT_PI;
    ];
    f64 => [
        PI              = core::f64::consts::PI;
        TAU             = core::f64::consts::TAU;
        FRAC_PI_2       = core::f64::consts::FRAC_PI_2;
        FRAC_PI_3       = core::f64::consts::FRAC_PI_3;
        FRAC_PI_4       = core::f64::consts::FRAC_PI_4;
        FRAC_PI_6       = core::f64::consts::FRAC_PI_6;
        FRAC_PI_8       = core::f64::consts::FRAC_PI_8;
        FRAC_1_PI       = core::f64::consts::FRAC_1_PI;
        FRAC_2_PI       = core::f64::consts::FRAC_2_PI;
        FRAC_2_SQRT_PI  = core::f64::consts::FRAC_2_SQRT_PI;
    ];
}
