/*!
Some useful (numerical) types and traits.

The traits are based on corresponding ones in [`num_traits`], but try to fill some gaps in the
super-traits and available constants.

As [`nalgebra`] unnecessarily provides many of the same methods as [`num_traits`], to avoid having
to refer to the methods with the full path, it is often necesary to use [`ToNalgebraRealField`][crate::nalgebra_support::ToNalgebraRealField] to hide the nalgebra implementations until
absolutely necessary to use nalgebra.
*/

//use trait_set::trait_set;
pub use num_traits::Float as NumTraitsFloat; // needed to re-export functions.
pub use num_traits::cast::AsPrimitive;

/// Typical integer type
#[allow(non_camel_case_types)]
pub type int = i64;

/// Typical unsigned integer type
#[allow(non_camel_case_types)]
pub type uint = u64;

/// Typical floating point number type
#[allow(non_camel_case_types)]
pub type float = f64;

/// Casts of abstract numerical types to others via the standard `as` keyword.
pub trait CastFrom<T : 'static + Copy> : num_traits::cast::AsPrimitive<T> {
    fn cast_from(other : T) -> Self;
}

macro_rules! impl_casts {
    ($($type:ty)*) => { $(
        impl_casts!(@phase2, $type,
                    u8 u16 u32 u64 u128 usize
                    i8 i16 i32 i64 i128 isize
                    f32 f64);
    )* };
    (@phase2, $type:ty, $($type2:ty)*) => { $(
        impl CastFrom<$type2> for $type {
            #[inline]
            fn cast_from(other : $type2) -> Self { other as $type }
        }
    )* };
}

impl_casts!(u8 u16 u32 u64 u128 usize
            i8 i16 i32 i64 i128 isize
            f32 f64);

/// Trait for general numeric types
pub trait Num : 'static + Copy + num::Num + num_traits::NumAssign
                 + std::iter::Sum + std::iter::Product
                 + std::fmt::Debug + std::fmt::Display + serde::Serialize
                 + CastFrom<u8>   + CastFrom<u16> + CastFrom<u32> + CastFrom<u64>
                 + CastFrom<u128> + CastFrom<usize>
                 + CastFrom<i8>   + CastFrom<i16> + CastFrom<i32> + CastFrom<i64>
                 + CastFrom<i128> + CastFrom<isize>
                 + CastFrom<f32>  + CastFrom<f64> {

    const ZERO : Self;
    const ONE : Self;
    const TWO : Self;
    /// Generic version of <Self>::MAX;
    const RANGE_MAX : Self;
    /// Generic version of <Self>::MIN;
    const RANGE_MIN : Self;
}

/// Trait for signed numeric types
pub trait SignedNum : Num + num::Signed + std::ops::Neg<Output=Self> {}
impl<U : Num + num::Signed + std::ops::Neg<Output=Self>> SignedNum for U { }

/// Trait for floating point numbers
pub trait Float : SignedNum + num::Float /*+ From<Self::CompatibleSize>*/ {
    // An unsigned integer that can be used for indexing operations and
    // converted to F without loss.
    //type CompatibleSize : CompatibleUnsigned<Self>;

    const PI : Self;
    const E : Self;
    const EPSILON : Self;
    const SQRT_2 : Self;
    const INFINITY : Self;
    const NEG_INFINITY : Self;
    const FRAC_2_SQRT_PI : Self;
}

/// Trait for integers
pub trait Integer : Num + num::Integer {}

/// Trait for unsigned integers
pub trait Unsigned : Num + Integer + num::Unsigned {}

/// Trait for signed integers
pub trait Signed : SignedNum + Integer {}

macro_rules! impl_num_consts {
    ($($type:ty)*) => { $(
        impl Num for $type {
            const ZERO : Self = 0 as $type;
            const ONE : Self = 1 as $type;
            const TWO : Self = 2 as $type;
            const RANGE_MAX : Self = <$type>::MAX;
            const RANGE_MIN : Self = <$type>::MIN;
        }
    )* }
}

macro_rules! impl_integers {
    ($signed:ty : $($type:ty)*) => { $(
        impl_num_consts!($type);
        impl Integer for $type {}
        impl $signed for $type {}
    )* }
}

impl_integers!(Signed: i8 i16 i32 i64 i128 isize);
impl_integers!(Unsigned: u8 u16 u32 u64 u128 usize);

impl_num_consts!(f32 f64);

impl Float for f64 {
    /*#[cfg(any(target_pointer_width = "128", target_pointer_width = "64"))]
    type CompatibleSize = u32;
    #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
    type CompatibleSize = usize;*/

    const PI : Self = std::f64::consts::PI;
    const E : Self = std::f64::consts::E;
    const EPSILON : Self = std::f64::EPSILON;
    const SQRT_2 : Self = std::f64::consts::SQRT_2;
    const INFINITY : Self = std::f64::INFINITY;
    const NEG_INFINITY : Self = std::f64::NEG_INFINITY;
    const FRAC_2_SQRT_PI : Self = std::f64::consts::FRAC_2_SQRT_PI;
}

impl Float for f32 {
    /*
    #[cfg(any(target_pointer_width = "128", target_pointer_width = "64", target_pointer_width = "32"))]
    type CompatibleSize = u16;
    #[cfg(any(target_pointer_width = "16"))]
    type CompatibleSize = usize;
    */

    const PI : Self = std::f32::consts::PI;
    const E : Self = std::f32::consts::E;
    const EPSILON : Self = std::f32::EPSILON;
    const SQRT_2 : Self = std::f32::consts::SQRT_2;
    const INFINITY : Self = std::f32::INFINITY;
    const NEG_INFINITY : Self = std::f32::NEG_INFINITY;
    const FRAC_2_SQRT_PI : Self = std::f32::consts::FRAC_2_SQRT_PI;
}

/*
trait_set! {
    pub trait CompatibleUnsigned<F : Float> = Unsigned + Into<F>;
    pub trait CompatibleSigned<F : Float> = Signed + Into<F>;
}
*/
