use std::f64;
use std::f32;

mod nullable_nullable_ops;
mod nullable_primitive_ops;
use traits::NullStorable;

#[derive(Debug, PartialEq)]
pub enum Nullable<T: NullStorable> {
    Value(T),
    Null,
}

impl NullStorable for i64 {}
impl NullStorable for i32 {}
impl NullStorable for i16 {}
impl NullStorable for i8 {}
impl NullStorable for isize {}
impl NullStorable for u64 {}
impl NullStorable for u32 {}
impl NullStorable for u16 {}
impl NullStorable for u8 {}
impl NullStorable for usize {}
impl NullStorable for bool {}
impl NullStorable for String {}
impl NullStorable for f64 {
    fn has_primitive_null() -> bool {
        true
    }

    fn is_null(&self) -> bool {
        self.is_nan()
    }

    fn is_not_null(&self) -> bool {
        !self.is_nan()
    }
}

impl NullStorable for f32 {
    fn has_primitive_null() -> bool {
        true
    }

    fn is_null(&self) -> bool {
        self.is_nan()
    }

    fn is_not_null(&self) -> bool {
        !self.is_nan()
    }
}

/// /////////////////////////////////////////////////////////////////////////////
/// Basic impl
/// /////////////////////////////////////////////////////////////////////////////

impl<T: NullStorable> Nullable<T> {
    fn new(value: T) -> Self {
        if value.is_null() {
            Nullable::Null
        } else {
            Nullable::Value(value)
        }
    }
}

/// /////////////////////////////////////////////////////////////////////////////
/// Basic conversion
/// /////////////////////////////////////////////////////////////////////////////

impl<T: NullStorable> From<T> for Nullable<T> {
    fn from(value: T) -> Self {
        Nullable::new(value)
    }
}

macro_rules! impl_from_never_nullable {
    ($t:ident) => {

        impl From<Nullable<$t>> for $t {
            fn from(value: Nullable<$t>) -> $t {
                match value {
                    Nullable::Value(val) => val,
                    // ToDo: must be TryFrom
                    _ => panic!("Unable to convert NaN to int")
                }
            }
        }
    }
}
macro_dispatch!(impl_from_never_nullable,
                i64,
                i32,
                i16,
                i8,
                isize,
                u64,
                u32,
                u16,
                u8,
                usize,
                bool,
                String);

macro_rules! impl_from_nullable {
    ($t:ident) => {

        impl From<Nullable<$t>> for $t {
            fn from(value: Nullable<$t>) -> $t {
                match value {
                    Nullable::Value(val) => val,
                    Nullable::Null => $t::NAN
                }
            }
        }
    }
}
macro_dispatch!(impl_from_nullable, f64, f32);

// Eq

impl<T: NullStorable> Eq for Nullable<T> where T: Eq {}

#[cfg(test)]
mod tests {

    use std::f64;
    use super::Nullable;

    #[test]
    fn test_int() {
        let i1 = Nullable::new(3);
        let i2 = Nullable::new(3);
        assert_eq!(i1, i1);
        assert_eq!(i1, i2);
        assert_eq!(i1, Nullable::new(3));

        assert!(i1 != Nullable::Null);
    }

    #[test]
    fn test_float() {
        let i1 = Nullable::new(1.1);
        let i2 = Nullable::new(f64::NAN);
        assert_eq!(i1, Nullable::Value(1.1));
        assert_eq!(i2, Nullable::Null);
    }

    #[test]
    fn test_int_conv_from() {
        let i = Nullable::<i64>::from(3);
        assert_eq!(i, Nullable::new(3));
        let o = i64::from(i);
        assert_eq!(o, 3);
    }

    #[test]
    fn test_float_conv_from() {
        let f = Nullable::<f64>::from(3.0);
        assert_eq!(f, Nullable::new(3.0));
        let o = f64::from(f);
        assert_eq!(o, 3.0);

        let nf = Nullable::<f64>::from(f64::NAN);
        assert_eq!(nf, Nullable::Null);
        let no = f64::from(nf);
        assert_eq!(no.is_nan(), true);
    }

    #[test]
    fn test_int_ops_add() {
        let i1 = Nullable::Value(3);
        let i2 = Nullable::Value(2);
        assert_eq!(i1 + i2, Nullable::Value(5));

        let i3 = Nullable::Value(3);
        assert_eq!(i3 + Nullable::Null, Nullable::Null);
        let i4 = Nullable::Value(3);
        assert_eq!(Nullable::Null + i4, Nullable::Null);

        let n: Nullable<i64> = Nullable::Null;
        assert_eq!(n + Nullable::Null, Nullable::Null);
    }
}
