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

use serde::{Deserialize, Serialize};

pub use super::numeric::{Float, Numeric};

/* Angle */

#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Angle<T> {
    Deg(T),
    Rad(T),
}

impl<T> Angle<T>
where
    T: Numeric,
{
    pub fn into_inner(self) -> T {
        match self {
            Self::Deg(v) => v,
            Self::Rad(v) => v,
        }
    }

    pub fn into_deg(self) -> Self {
        match self {
            Self::Deg(v) => Self::Deg(v),
            Self::Rad(v) => Self::Rad(T::to_degrees(v)),
        }
    }

    pub fn into_rad(self) -> Self {
        match self {
            Self::Deg(v) => Self::Deg(T::to_radians(v)),
            Self::Rad(v) => Self::Rad(v),
        }
    }

    pub fn abs(self) -> Self {
        match self {
            Self::Deg(value) => Self::Deg(value.abs()),
            Self::Rad(value) => Self::Rad(value.abs()),
        }
    }
}

impl<T> Angle<T>
where
    T: Float,
{
    pub fn sin(self) -> T {
        T::sin(self.into_rad().into_inner())
    }

    pub fn cos(self) -> T {
        T::cos(self.into_rad().into_inner())
    }
}

impl<T> Neg for Angle<T>
where
    T: Neg + Numeric,
{
    type Output = Angle<T>;

    fn neg(self) -> Self::Output {
        match self {
            Self::Deg(value) => Self::Deg(-value),
            Self::Rad(value) => Self::Rad(-value),
        }
    }
}

impl<T> Add for Angle<T>
where
    T: Add<T, Output = T> + Numeric,
{
    type Output = Angle<T>;

    fn add(self, v: Self) -> Self::Output {
        match self {
            Self::Deg(value) => Self::Deg(value + v.into_deg().into_inner()),
            Self::Rad(value) => Self::Rad(value + v.into_rad().into_inner()),
        }
    }
}

impl<T> Sub for Angle<T>
where
    T: Sub<T, Output = T> + Numeric,
{
    type Output = Angle<T>;

    fn sub(self, v: Self) -> Self::Output {
        match self {
            Self::Deg(value) => Self::Deg(value - v.into_deg().into_inner()),
            Self::Rad(value) => Self::Rad(value - v.into_rad().into_inner()),
        }
    }
}

impl<T> Mul<T> for Angle<T>
where
    T: Mul<T, Output = T> + Numeric,
{
    type Output = Angle<T>;

    fn mul(self, factor: T) -> Self::Output {
        match self {
            Self::Deg(value) => Self::Deg(value * factor),
            Self::Rad(value) => Self::Rad(value * factor),
        }
    }
}

impl<T> Div<T> for Angle<T>
where
    T: Div<T, Output = T> + Numeric,
{
    type Output = Angle<T>;

    fn div(self, factor: T) -> Self::Output {
        match self {
            Self::Deg(value) => Self::Deg(value / factor),
            Self::Rad(value) => Self::Rad(value / factor),
        }
    }
}
