use crate::{ContextRef, Error, Result, Value, ValueRef, ValueType};
use std::num::TryFromIntError;

pub trait TryFromValue: Sized {
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self>;
}

impl TryFromValue for u8 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        let v = value.to_u32(&ctx)?;
        v.try_into()
            .map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
    }
}

impl TryFromValue for u16 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        let v = value.to_u32(&ctx)?;
        v.try_into()
            .map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
    }
}

impl TryFromValue for u32 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        value.to_u32(&ctx)
    }
}

// impl<'c,'d> TryFrom<&'c ValueRef<'d>> for u64 {
//     #[inline]
//     fn try_from_value(value: &ValueRef, ctx:&ContextRef) -> Result< Self> {
//         value.to_u64()
//     }
// }

impl TryFromValue for i8 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        let v = value.to_i32(&ctx)?;
        v.try_into()
            .map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
    }
}

impl TryFromValue for i16 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        let v = value.to_i32(&ctx)?;
        v.try_into()
            .map_err(|e: TryFromIntError| Error::ConversionError(e.to_string()))
    }
}

impl TryFromValue for i32 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        value.to_i32(&ctx)
    }
}

impl TryFromValue for i64 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        value.to_i64(&ctx)
    }
}

impl TryFromValue for f32 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        let v = value.to_float64(&ctx)?;
        Ok(v as f32)
    }
}

impl TryFromValue for f64 {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        value.to_float64(&ctx)
    }
}

impl TryFromValue for bool {
    #[inline]
    fn try_from_value(value: &ValueRef, _ctx: &ContextRef) -> Result<Self> {
        value.to_bool()
    }
}

impl TryFromValue for String {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        value.to_string(&ctx)
    }
}

impl TryFromValue for Value {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        Ok(value.dup(ctx))
    }
}

impl TryFromValue for () {
    #[inline]
    fn try_from_value(value: &ValueRef, _ctx: &ContextRef) -> Result<Self> {
        if value.is_undefined() {
            Ok(())
        } else {
            Err(Error::InvalidType {
                expected: ValueType::Undefined,
                found: value.value_type(),
            })
        }
    }
}

impl<T: TryFromValue> TryFromValue for Option<T> {
    #[inline]
    fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result<Self> {
        if value.is_null() || value.is_undefined() {
            Ok(None)
        } else {
            Ok(Some(T::try_from_value(value, ctx)?))
        }
    }
}
