//! Coerce a `Value` into some concrete types.
//!
//! These operations are cheap when the captured value is a simple primitive,
//! but may end up executing arbitrary caller code if the value is complex.
//! They will also attempt to downcast erased types into a primitive where possible.

use std::any::TypeId;
use std::fmt;

use super::{Erased, Inner, Primitive, Visitor};
use crate::kv::value::{Error, Value};

impl<'v> Value<'v> {
    /// Try get a `usize` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_usize(&self) -> Option<usize> {
        self.inner
            .cast()
            .into_primitive()
            .into_u64()
            .map(|v| v as usize)
    }

    /// Try get a `u8` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_u8(&self) -> Option<u8> {
        self.inner
            .cast()
            .into_primitive()
            .into_u64()
            .map(|v| v as u8)
    }

    /// Try get a `u16` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_u16(&self) -> Option<u16> {
        self.inner
            .cast()
            .into_primitive()
            .into_u64()
            .map(|v| v as u16)
    }

    /// Try get a `u32` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_u32(&self) -> Option<u32> {
        self.inner
            .cast()
            .into_primitive()
            .into_u64()
            .map(|v| v as u32)
    }

    /// Try get a `u64` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_u64(&self) -> Option<u64> {
        self.inner.cast().into_primitive().into_u64()
    }

    /// Try get a `isize` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_isize(&self) -> Option<isize> {
        self.inner
            .cast()
            .into_primitive()
            .into_i64()
            .map(|v| v as isize)
    }

    /// Try get a `i8` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_i8(&self) -> Option<i8> {
        self.inner
            .cast()
            .into_primitive()
            .into_i64()
            .map(|v| v as i8)
    }

    /// Try get a `i16` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_i16(&self) -> Option<i16> {
        self.inner
            .cast()
            .into_primitive()
            .into_i64()
            .map(|v| v as i16)
    }

    /// Try get a `i32` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_i32(&self) -> Option<i32> {
        self.inner
            .cast()
            .into_primitive()
            .into_i64()
            .map(|v| v as i32)
    }

    /// Try get a `i64` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_i64(&self) -> Option<i64> {
        self.inner.cast().into_primitive().into_i64()
    }

    /// Try get a `f32` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_f32(&self) -> Option<f32> {
        self.inner
            .cast()
            .into_primitive()
            .into_f64()
            .map(|v| v as f32)
    }

    /// Try get a `f64` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_f64(&self) -> Option<f64> {
        self.inner.cast().into_primitive().into_f64()
    }

    /// Try get a `bool` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_bool(&self) -> Option<bool> {
        self.inner.cast().into_primitive().into_bool()
    }

    /// Try get a `char` from this value.
    ///
    /// This method is cheap for primitive types, but may call arbitrary
    /// serialization implementations for complex ones.
    pub fn to_char(&self) -> Option<char> {
        self.inner.cast().into_primitive().into_char()
    }

    /// Try get a `str` from this value.
    ///
    /// This method is cheap for primitive types. It won't allocate an owned
    /// `String` if the value is a complex type.
    pub fn to_borrowed_str(&self) -> Option<&str> {
        self.inner.cast().into_primitive().into_borrowed_str()
    }
}

impl<'v> Inner<'v> {
    /// Cast the inner value to another type.
    fn cast(self) -> Cast<'v> {
        struct CastVisitor<'v>(Cast<'v>);

        impl<'v> Visitor<'v> for CastVisitor<'v> {
            fn debug(&mut self, _: &dyn fmt::Debug) -> Result<(), Error> {
                Ok(())
            }

            fn u64(&mut self, v: u64) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Unsigned(v));
                Ok(())
            }

            fn i64(&mut self, v: i64) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Signed(v));
                Ok(())
            }

            fn f64(&mut self, v: f64) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Float(v));
                Ok(())
            }

            fn bool(&mut self, v: bool) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Bool(v));
                Ok(())
            }

            fn char(&mut self, v: char) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Char(v));
                Ok(())
            }

            fn borrowed_str(&mut self, v: &'v str) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::Str(v));
                Ok(())
            }

            #[cfg(not(feature = "std"))]
            fn str(&mut self, _: &str) -> Result<(), Error> {
                Ok(())
            }

            #[cfg(feature = "std")]
            fn str(&mut self, v: &str) -> Result<(), Error> {
                self.0 = Cast::String(v.into());
                Ok(())
            }

            fn none(&mut self) -> Result<(), Error> {
                self.0 = Cast::Primitive(Primitive::None);
                Ok(())
            }

            #[cfg(feature = "kv_unstable_sval")]
            fn sval(&mut self, v: &dyn super::sval::Value) -> Result<(), Error> {
                self.0 = super::sval::cast(v);
                Ok(())
            }
        }

        // Try downcast an erased value first
        // It also lets us avoid the Visitor infrastructure for simple primitives
        let primitive = match self {
            Inner::Primitive(value) => Some(value),
            Inner::Fill(value) => value.downcast_primitive(),
            Inner::Debug(value) => value.downcast_primitive(),
            Inner::Display(value) => value.downcast_primitive(),

            #[cfg(feature = "sval")]
            Inner::Sval(value) => value.downcast_primitive(),
        };

        primitive.map(Cast::Primitive).unwrap_or_else(|| {
            // If the erased value isn't a primitive then we visit it
            let mut cast = CastVisitor(Cast::Primitive(Primitive::None));
            let _ = self.visit(&mut cast);
            cast.0
        })
    }
}

pub(super) enum Cast<'v> {
    Primitive(Primitive<'v>),
    #[cfg(feature = "std")]
    String(String),
}

impl<'v> Cast<'v> {
    fn into_primitive(self) -> Primitive<'v> {
        match self {
            Cast::Primitive(value) => value,
            #[cfg(feature = "std")]
            _ => Primitive::None,
        }
    }
}

impl<'v> Primitive<'v> {
    fn into_borrowed_str(self) -> Option<&'v str> {
        if let Primitive::Str(value) = self {
            Some(value)
        } else {
            None
        }
    }

    fn into_u64(self) -> Option<u64> {
        match self {
            Primitive::Unsigned(value) => Some(value),
            Primitive::Signed(value) => Some(value as u64),
            Primitive::Float(value) => Some(value as u64),
            _ => None,
        }
    }

    fn into_i64(self) -> Option<i64> {
        match self {
            Primitive::Signed(value) => Some(value),
            Primitive::Unsigned(value) => Some(value as i64),
            Primitive::Float(value) => Some(value as i64),
            _ => None,
        }
    }

    fn into_f64(self) -> Option<f64> {
        match self {
            Primitive::Float(value) => Some(value),
            Primitive::Unsigned(value) => Some(value as f64),
            Primitive::Signed(value) => Some(value as f64),
            _ => None,
        }
    }

    fn into_char(self) -> Option<char> {
        if let Primitive::Char(value) = self {
            Some(value)
        } else {
            None
        }
    }

    fn into_bool(self) -> Option<bool> {
        if let Primitive::Bool(value) = self {
            Some(value)
        } else {
            None
        }
    }
}

impl<'v, T: ?Sized + 'static> Erased<'v, T> {
    // NOTE: This function is a perfect candidate for memoization
    // The outcome could be stored in a `Cell<Primitive>`
    fn downcast_primitive(self) -> Option<Primitive<'v>> {
        macro_rules! type_ids {
            ($($value:ident : $ty:ty => $cast:expr,)*) => {{
                struct TypeIds;

                impl TypeIds {
                    fn downcast_primitive<'v, T: ?Sized>(&self, value: Erased<'v, T>) -> Option<Primitive<'v>> {
                        $(
                            if TypeId::of::<$ty>() == value.type_id {
                                let $value = unsafe { value.downcast_unchecked::<$ty>() };
                                return Some(Primitive::from($cast));
                            }
                        )*

                        None
                    }
                }

                TypeIds
            }};
        }

        let type_ids = type_ids![
            value: usize => *value as u64,
            value: u8 => *value as u64,
            value: u16 => *value as u64,
            value: u32 => *value as u64,
            value: u64 => *value,

            value: isize => *value as i64,
            value: i8 => *value as i64,
            value: i16 => *value as i64,
            value: i32 => *value as i64,
            value: i64 => *value,

            value: f32 => *value as f64,
            value: f64 => *value,

            value: char => *value,
            value: bool => *value,

            value: &str => *value,
        ];

        type_ids.downcast_primitive(self)
    }
}

#[cfg(feature = "std")]
mod std_support {
    use super::*;

    use std::borrow::Cow;

    impl<'v> Value<'v> {
        /// Try get a `usize` from this value.
        ///
        /// This method is cheap for primitive types, but may call arbitrary
        /// serialization implementations for complex ones. If the serialization
        /// implementation produces a short lived string it will be allocated.
        pub fn to_str(&self) -> Option<Cow<str>> {
            self.inner.cast().into_str()
        }
    }

    impl<'v> Cast<'v> {
        pub(super) fn into_str(self) -> Option<Cow<'v, str>> {
            match self {
                Cast::Primitive(Primitive::Str(value)) => Some(value.into()),
                Cast::String(value) => Some(value.into()),
                _ => None,
            }
        }
    }

    #[cfg(test)]
    mod tests {
        use crate::kv::ToValue;

        #[test]
        fn primitive_cast() {
            assert_eq!(
                "a string",
                "a string"
                    .to_owned()
                    .to_value()
                    .to_borrowed_str()
                    .expect("invalid value")
            );
            assert_eq!(
                "a string",
                &*"a string".to_value().to_str().expect("invalid value")
            );
            assert_eq!(
                "a string",
                &*"a string"
                    .to_owned()
                    .to_value()
                    .to_str()
                    .expect("invalid value")
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::kv::ToValue;

    #[test]
    fn primitive_cast() {
        assert_eq!(
            "a string",
            "a string"
                .to_value()
                .to_borrowed_str()
                .expect("invalid value")
        );
        assert_eq!(
            "a string",
            Some("a string")
                .to_value()
                .to_borrowed_str()
                .expect("invalid value")
        );

        assert_eq!(1u8, 1u64.to_value().to_u8().expect("invalid value"));
        assert_eq!(1u16, 1u64.to_value().to_u16().expect("invalid value"));
        assert_eq!(1u32, 1u64.to_value().to_u32().expect("invalid value"));
        assert_eq!(1u64, 1u64.to_value().to_u64().expect("invalid value"));
        assert_eq!(1usize, 1u64.to_value().to_usize().expect("invalid value"));

        assert_eq!(-1i8, -1i64.to_value().to_i8().expect("invalid value"));
        assert_eq!(-1i16, -1i64.to_value().to_i16().expect("invalid value"));
        assert_eq!(-1i32, -1i64.to_value().to_i32().expect("invalid value"));
        assert_eq!(-1i64, -1i64.to_value().to_i64().expect("invalid value"));
        assert_eq!(-1isize, -1i64.to_value().to_isize().expect("invalid value"));

        assert!(1f32.to_value().to_f32().is_some(), "invalid value");
        assert!(1f64.to_value().to_f64().is_some(), "invalid value");

        assert_eq!(1u32, 1i64.to_value().to_u32().expect("invalid value"));
        assert_eq!(1i32, 1u64.to_value().to_i32().expect("invalid value"));
        assert!(1f32.to_value().to_i32().is_some(), "invalid value");

        assert_eq!('a', 'a'.to_value().to_char().expect("invalid value"));
        assert_eq!(true, true.to_value().to_bool().expect("invalid value"));
    }
}
