use super::Result;
use crate::{error, R32, R64};
use float_cmp::ApproxEq;
use std;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged, from = "DeserializeItem")]
pub enum Item {
    Nothing,
    B(bool),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    F32(R32),
    F64(R64),
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum DeserializeItem {
    Nothing,
    B(bool),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    F64(f64),
}

impl From<DeserializeItem> for Item {
    fn from(deserialized: DeserializeItem) -> Item {
        use DeserializeItem::*;
        match deserialized {
            F64(v) => Item::F64(R64::from(v)),
            Nothing => Item::Nothing,
            B(v) => Item::B(v),
            U8(v) => Item::U8(v),
            U16(v) => Item::U16(v),
            U32(v) => Item::U32(v),
            U64(v) => Item::U64(v),
            I8(v) => Item::I8(v),
            I16(v) => Item::I16(v),
            I32(v) => Item::I32(v),
            I64(v) => Item::I64(v),
        }
    }
}

impl Default for Item {
    fn default() -> Self {
        Item::Nothing
    }
}

impl From<bool> for Item {
    fn from(v: bool) -> Self {
        Item::B(v)
    }
}

impl From<i8> for Item {
    fn from(v: i8) -> Self {
        Item::I8(v)
    }
}

impl From<i16> for Item {
    fn from(v: i16) -> Self {
        Item::I16(v)
    }
}

impl From<i32> for Item {
    fn from(v: i32) -> Self {
        Item::I32(v)
    }
}

impl From<i64> for Item {
    fn from(v: i64) -> Self {
        Item::I64(v)
    }
}

impl From<u8> for Item {
    fn from(v: u8) -> Self {
        Item::U8(v)
    }
}

impl From<u16> for Item {
    fn from(v: u16) -> Self {
        Item::U16(v)
    }
}

impl From<u32> for Item {
    fn from(v: u32) -> Self {
        Item::U32(v)
    }
}

impl From<u64> for Item {
    fn from(v: u64) -> Self {
        Item::U64(v)
    }
}

impl From<f64> for Item {
    fn from(v: f64) -> Self {
        Item::F64(R64::from(v))
    }
}

impl From<R64> for Item {
    fn from(v: R64) -> Self {
        Item::F64(v.into())
    }
}

impl From<f32> for Item {
    fn from(v: f32) -> Self {
        Item::F32(R32::from(v))
    }
}

impl From<R32> for Item {
    fn from(v: R32) -> Self {
        Item::F32(v)
    }
}

impl<T> From<Option<T>> for Item
where
    Item: From<T>,
{
    fn from(v: Option<T>) -> Self {
        match v {
            Some(v) => Item::from(v),
            None => Item::Nothing,
        }
    }
}

impl Item {
    pub fn is_bool(&self) -> bool {
        use crate::Item::*;
        match *self {
            B(_) => true,
            _ => false,
        }
    }

    pub fn is_numeric(&self) -> bool {
        use crate::Item::*;
        match *self {
            F32(_) | F64(_) | U8(_) | U16(_) | U32(_) | U64(_) | I8(_)
            | I16(_) | I32(_) | I64(_) => true,
            B(_) | Nothing => false,
        }
    }

    pub fn is_float(&self) -> bool {
        use crate::Item::*;
        match *self {
            F32(_) | F64(_) => true,
            Nothing | B(_) | U8(_) | U16(_) | U32(_) | U64(_) | I8(_)
            | I16(_) | I32(_) | I64(_) => false,
        }
    }

    pub fn can_convert_to_f64(&self) -> bool {
        use crate::Item::*;
        match *self {
            B(_) | F32(_) | F64(_) | U8(_) | U16(_) | U32(_) | U64(_)
            | I8(_) | I16(_) | I32(_) | I64(_) => true,
            Nothing => false,
        }
    }

    pub fn to_u64(&self) -> Result<u64> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(if v { 1u64 } else { 0u64 }),
            F32(v) if v >= 0f32 => Ok(v.into_inner() as u64),
            F64(v) if v >= 0f64 => Ok(v.into_inner() as u64),
            U8(v) => Ok(u64::from(v)),
            U16(v) => Ok(u64::from(v)),
            U32(v) => Ok(u64::from(v)),
            U64(v) => Ok(v),
            I8(v) if v >= 0 => Ok(v as u64),
            I16(v) if v >= 0 => Ok(v as u64),
            I32(v) if v >= 0 => Ok(v as u64),
            I64(v) if v >= 0 => Ok(v as u64),
            _ => error::ConversionError {
                function: "to_u64".to_string(),
                value: self.clone(),
                message: "Not a positive number".to_string(),
            }
            .fail(),
        }
    }

    pub fn to_usize(&self) -> Result<usize> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(if v { 1usize } else { 0usize }),
            F32(v) if v >= 0f32 => Ok(v.into_inner() as usize),
            F64(v) if v >= 0f64 => Ok(v.into_inner() as usize),
            U8(v) => Ok(v as usize),
            U16(v) => Ok(v as usize),
            U32(v) => Ok(v as usize),
            U64(v) => Ok(v as usize),
            I8(v) if v >= 0 => Ok(v as usize),
            I16(v) if v >= 0 => Ok(v as usize),
            I32(v) if v >= 0 => Ok(v as usize),
            I64(v) if v >= 0 => Ok(v as usize),
            _ => error::ConversionError {
                function: "to_u64".to_string(),
                value: self.clone(),
                message: "Not a positive number".to_string(),
            }
            .fail(),
        }
    }

    pub fn to_bool(&self) -> Result<bool> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(v),
            F32(v) => Ok(v != 0f32),
            F64(v) => Ok(v != 0f64),
            U8(v) => Ok(v != 0),
            U16(v) => Ok(v != 0),
            U32(v) => Ok(v != 0),
            U64(v) => Ok(v != 0),
            I8(v) => Ok(v != 0),
            I16(v) => Ok(v != 0),
            I32(v) => Ok(v != 0),
            I64(v) => Ok(v != 0),
            Nothing => Ok(false),
        }
    }

    pub fn to_float(&self) -> Result<R64> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(R64::from(if v { 1f64 } else { 0f64 })),
            F32(v) => Ok(R64::from(v.into_inner() as f64)),
            F64(v) => Ok(v),
            U8(v) => Ok(R64::from(v as f64)),
            U16(v) => Ok(R64::from(v as f64)),
            U32(v) => Ok(R64::from(v as f64)),
            U64(v) => Ok(R64::from(v as f64)),
            I8(v) => Ok(R64::from(v as f64)),
            I16(v) => Ok(R64::from(v as f64)),
            I32(v) => Ok(R64::from(v as f64)),
            I64(v) => Ok(R64::from(v as f64)),
            Nothing => error::ConversionError {
                function: "to_float".to_string(),
                value: self.clone(),
                message: "Not a numeric".to_string(),
            }
            .fail(),
        }
    }

    pub fn is_unsigned(&self) -> bool {
        use crate::Item::*;
        match *self {
            B(_) | U8(_) | U16(_) | U32(_) | U64(_) => true,
            Nothing | F32(_) | F64(_) | I8(_) | I16(_) | I32(_)
            | I64(_) => false,
        }
    }

    pub fn to_unsigned(&self) -> Result<u64> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(if v { 1u64 } else { 0u64 }),
            U8(v) => Ok(u64::from(v)),
            U16(v) => Ok(u64::from(v)),
            U32(v) => Ok(u64::from(v)),
            U64(v) => Ok(v as u64),
            F32(v) => Ok(v.into_inner() as u64),
            F64(v) => Ok(v.into_inner() as u64),
            I8(v) => Ok(v as u64),
            I16(v) => Ok(v as u64),
            I32(v) => Ok(v as u64),
            I64(v) => Ok(v as u64),
            Nothing => error::ConversionError {
                function: "to_unsigned".to_string(),
                value: self.clone(),
                message: "Not a numeric".to_string(),
            }
            .fail(),
        }
    }

    pub fn is_signed(&self) -> bool {
        use crate::Item::*;
        match *self {
            I8(_) | I16(_) | I32(_) | I64(_) => true,
            Nothing | B(_) | F32(_) | F64(_) | U8(_) | U16(_) | U32(_)
            | U64(_) => false,
        }
    }

    pub fn to_signed(&self) -> Result<i64> {
        use crate::Item::*;
        match *self {
            B(v) => Ok(if v { 1i64 } else { 0i64 }),
            I8(v) => Ok(i64::from(v)),
            I16(v) => Ok(i64::from(v)),
            I32(v) => Ok(i64::from(v)),
            I64(v) => Ok(v as i64),
            F32(v) => Ok(v.into_inner() as i64),
            F64(v) => Ok(v.into_inner() as i64),
            U8(v) => Ok(i64::from(v)),
            U16(v) => Ok(i64::from(v)),
            U32(v) => Ok(i64::from(v)),
            U64(v) => Ok(v as i64),
            Nothing => error::ConversionError {
                function: "to_signed".to_string(),
                value: self.clone(),
                message: "Not a numeric".to_string(),
            }
            .fail(),
        }
    }

    pub fn counts_as_true(&self) -> bool {
        use crate::Item::*;
        match *self {
            B(v) => (v),
            I8(v) => (v != 0i8),
            I16(v) => (v != 0i16),
            I32(v) => (v != 0i32),
            I64(v) => (v != 0i64),
            F32(v) => (v != 0f32),
            F64(v) => (v != 0f64),
            U8(v) => (v != 0u8),
            U16(v) => (v != 0u16),
            U32(v) => (v != 0),
            U64(v) => (v != 0),
            Nothing => (false),
        }
    }
}

pub trait TwoItemsExt {
    fn common_type(&self) -> Self;
}

impl TwoItemsExt for (Item, Item) {
    fn common_type(&self) -> Self {
        let &(ref a, ref b) = self;
        if a.is_numeric() && b.is_numeric() {
            if a.is_float() || b.is_float() {
                (
                    a.to_float().unwrap().into(),
                    b.to_float().unwrap().into(),
                )
            } else if a.is_signed() || b.is_signed() {
                (
                    a.to_signed().unwrap().into(),
                    b.to_signed().unwrap().into(),
                )
            } else if a.is_unsigned() || b.is_unsigned() {
                (
                    a.to_unsigned().unwrap().into(),
                    b.to_unsigned().unwrap().into(),
                )
            } else {
                (Item::Nothing, Item::Nothing)
            }
        } else {
            (Item::Nothing, Item::Nothing)
        }
    }
}

impl PartialEq for Item {
    fn eq(&self, other: &Item) -> bool {
        use crate::Item::*;
        match (self, other) {
            (&B(true), &B(true))
            | (&B(false), &B(false))
            | (&Nothing, &Nothing) => true,
            (&Nothing, _) | (_, &Nothing) => false,
            (s, o) => match ((*s).clone(), (*o).clone()).common_type() {
                (I8(a), I8(b)) => a.eq(&b),
                (I16(a), I16(b)) => a.eq(&b),
                (I32(a), I32(b)) => a.eq(&b),
                (I64(a), I64(b)) => a.eq(&b),
                (U8(a), U8(b)) => a.eq(&b),
                (U16(a), U16(b)) => a.eq(&b),
                (U32(a), U32(b)) => a.eq(&b),
                (U64(a), U64(b)) => a.eq(&b),
                (F32(a), F32(b)) => a.into_inner().approx_eq(
                    b.into_inner(),
                    float_cmp::F32Margin {
                        epsilon: 3.0 * std::f32::EPSILON,
                        ulps: 3,
                    },
                ),
                (F64(a), F64(b)) => a.into_inner().approx_eq(
                    b.into_inner(),
                    float_cmp::F64Margin {
                        epsilon: 3.0 * std::f64::EPSILON,
                        ulps: 3,
                    },
                ),
                _ => false,
            },
        }
    }
}
impl Eq for Item {}

impl PartialOrd for Item {
    fn partial_cmp(&self, other: &Item) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl std::cmp::Ord for Item {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        use crate::Item::*;
        match (self, other) {
            (&Nothing, &Nothing) => std::cmp::Ordering::Equal,
            (&Nothing, _) => std::cmp::Ordering::Less,
            (_, &Nothing) => std::cmp::Ordering::Greater,
            (s, o) => match ((*s).clone(), (*o).clone()).common_type() {
                (I8(a), I8(b)) => a.cmp(&b),
                (I16(a), I16(b)) => a.cmp(&b),
                (I32(a), I32(b)) => a.cmp(&b),
                (I64(a), I64(b)) => a.cmp(&b),
                (U8(a), U8(b)) => a.cmp(&b),
                (U16(a), U16(b)) => a.cmp(&b),
                (U32(a), U32(b)) => a.cmp(&b),
                (U64(a), U64(b)) => a.cmp(&b),
                (F32(a), F32(b)) => a.cmp(&b),
                (F64(a), F64(b)) => a.cmp(&b),
                _ => std::cmp::Ordering::Equal,
            },
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{Item, R64};

    #[test]
    fn deserialize() {
        fn des(s: &str) -> serde_json::Result<Item> {
            serde_json::from_str(s)
        }
        assert_eq!(des("55").unwrap(), Item::U8(55));
        assert_eq!(des("-455").unwrap(), Item::I16(-455));
        assert_eq!(des("null").unwrap(), Item::Nothing);
        assert_eq!(des("6.28").unwrap(), Item::F64(R64::from(6.28)));
        assert_eq!(
            des("6.2831853071795862345645430898").unwrap(),
            Item::F64(R64::from(6.2831853071795862345645430898))
        );
        assert_eq!(des("true").unwrap(), Item::B(true));
        assert_eq!(des("false").unwrap(), Item::B(false));
    }

    #[test]
    fn eq_types() {
        use crate::Item::*;
        assert_eq!(I8(3i8), I8(3i8));
        assert_eq!(I8(3i8), U8(3u8));
        assert_eq!(U8(3u8), I8(3i8));
        assert_eq!(U16(3u16), I8(3i8));
        assert_eq!(U32(3u32), I8(3i8));
        assert_eq!(U64(3u64), I8(3i8));
    }

    #[test]
    fn ne_types() {
        use crate::Item::*;
        assert_ne!(Nothing, U8(3u8));
        assert_ne!(U8(3u8), Nothing);
    }

    #[test]
    fn comparison() {
        use crate::Item::*;
        assert!(I8(3i8) > I8(2i8));
        assert!(Nothing < I8(3i8));
        assert!(I8(3i8) > Nothing);
        assert!(Item::from(3.2f64) > Nothing);
        assert!(Nothing < Item::from(3.2f64));
        assert!(Item::from(-3.2f64) > Nothing);
        assert!(Nothing < Item::from(-3.2f64));
    }

    #[test]
    fn ensure_approx_eq_f64() {
        let z = Item::from(0f64);
        assert!(z == Item::from(0.00000000000000000000000000000001f64));
        assert!(z == Item::from(0.0000000000000000000000000000001f64));
        assert!(z == Item::from(0.000000000000000000000000000001f64));
        assert!(z == Item::from(0.00000000000000000000000000001f64));
        assert!(z == Item::from(0.0000000000000000000000000001f64));
        assert!(z == Item::from(0.000000000000000000000000001f64));
        assert!(z == Item::from(0.00000000000000000000000001f64));
        assert!(z == Item::from(0.0000000000000000000000001f64));
        assert!(z == Item::from(0.000000000000000000000001f64));
        assert!(z == Item::from(0.00000000000000000000001f64));
        assert!(z == Item::from(0.0000000000000000000001f64));
        assert!(z == Item::from(0.000000000000000000001f64));
        assert!(z == Item::from(0.00000000000000000001f64));
        assert!(z == Item::from(0.0000000000000000001f64));
        assert!(z == Item::from(0.000000000000000001f64));
        assert!(z == Item::from(0.00000000000000001f64));
        assert!(z == Item::from(0.0000000000000001f64));
        assert!(z == Item::from(0f64));
        assert!(z != Item::from(0.000000000000001f64));
        assert!(z != Item::from(0.00000000000001f64));
        assert!(z != Item::from(0.0000000000001f64));
        assert!(z != Item::from(0.000000000001f64));
        assert!(z != Item::from(0.00000000001f64));
        assert!(z != Item::from(0.0000000001f64));
        assert!(z != Item::from(0.000000001f64));
        assert!(z != Item::from(0.00000001f64));
        assert!(z != Item::from(0.0000001f64));
        assert!(z != Item::from(0.000001f64));
        assert!(z != Item::from(0.00001f64));
        assert!(z != Item::from(0.0001f64));
        assert!(z != Item::from(0.001f64));
        assert!(z != Item::from(0.01f64));
        assert!(z != Item::from(0.1f64));
        assert!(z != Item::from(1f64));
        assert!(z != Item::from(1.1f64));
        assert!(z != Item::from(999999f64));
    }

    #[test]
    fn ensure_approx_eq_f32() {
        let z = Item::from(0f32);
        assert!(z == Item::from(0.00000000000000000000000000000001f32));
        assert!(z == Item::from(0.0000000000000000000000000000001f32));
        assert!(z == Item::from(0.000000000000000000000000000001f32));
        assert!(z == Item::from(0.00000000000000000000000000001f32));
        assert!(z == Item::from(0.0000000000000000000000000001f32));
        assert!(z == Item::from(0.000000000000000000000000001f32));
        assert!(z == Item::from(0.00000000000000000000000001f32));
        assert!(z == Item::from(0.0000000000000000000000001f32));
        assert!(z == Item::from(0.000000000000000000000001f32));
        assert!(z == Item::from(0.00000000000000000000001f32));
        assert!(z == Item::from(0.0000000000000000000001f32));
        assert!(z == Item::from(0.000000000000000000001f32));
        assert!(z == Item::from(0.00000000000000000001f32));
        assert!(z == Item::from(0.0000000000000000001f32));
        assert!(z == Item::from(0.000000000000000001f32));
        assert!(z == Item::from(0.00000000000000001f32));
        assert!(z == Item::from(0.0000000000000001f32));
        assert!(z == Item::from(0f32));
        assert!(z != Item::from(0.000000000000001f32));
        assert!(z != Item::from(0.00000000000001f32));
        assert!(z != Item::from(0.0000000000001f32));
        assert!(z != Item::from(0.000000000001f32));
        assert!(z != Item::from(0.00000000001f32));
        assert!(z != Item::from(0.0000000001f32));
        assert!(z != Item::from(0.000000001f32));
        assert!(z != Item::from(0.00000001f32));
        assert!(z != Item::from(0.0000001f32));
        assert!(z != Item::from(0.000001f32));
        assert!(z != Item::from(0.00001f32));
        assert!(z != Item::from(0.0001f32));
        assert!(z != Item::from(0.001f32));
        assert!(z != Item::from(0.01f32));
        assert!(z != Item::from(0.1f32));
        assert!(z != Item::from(1f32));
        assert!(z != Item::from(1.1f32));
        assert!(z != Item::from(999999f32));
    }
}
