use crate::{Enumerable, Listable, Mappable, Structable, Tuplable, Valuable, Visit};

use core::fmt;

macro_rules! value {
    (
        $(
            $(#[$attrs:meta])*
            $variant:ident($ty:ty),
        )*
    ) => {
        /// Any Rust value
        ///
        /// The `Value` enum is used to pass single values to the
        /// [visitor][`Visit`]. Primitive types are enumerated and other types
        /// are represented at trait objects.
        ///
        /// Values are converted to `Value` instances using
        /// [`Valuable::as_value()`].
        ///
        /// # Examples
        ///
        /// Convert a primitive type
        ///
        /// ```
        /// use valuable::{Value, Valuable};
        ///
        /// let num = 123;
        /// let val = num.as_value();
        ///
        /// assert!(matches!(val, Value::I32(v) if v == 123));
        /// ```
        ///
        /// Converting a struct
        ///
        /// ```
        /// use valuable::{Value, Valuable};
        ///
        /// #[derive(Valuable, Debug)]
        /// struct HelloWorld {
        ///     message: String,
        /// }
        ///
        /// let hello = HelloWorld {
        ///     message: "greetings".to_string(),
        /// };
        ///
        /// let val = hello.as_value();
        ///
        /// assert!(matches!(val, Value::Structable(_v)));
        ///
        /// // The Value `Debug` output matches the struct's
        /// assert_eq!(
        ///     format!("{:?}", val),
        ///     format!("{:?}", hello),
        /// );
        /// ```
        ///
        /// [visitor]: Visit
        #[non_exhaustive]
        #[derive(Clone, Copy)]
        pub enum Value<'a> {
            $(
                $(#[$attrs])*
                $variant($ty),
            )*

            /// A Rust `()` or `None` value.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// let v = Value::Unit;
            /// ```
            Unit,
        }

        $(
            $(#[$attrs])*
            impl<'a> From<$ty> for Value<'a> {
                fn from(src: $ty) -> Value<'a> {
                    Value::$variant(src)
                }
            }
        )*

        impl<'a> From<()> for Value<'a> {
            fn from(_: ()) -> Value<'a> {
                Value::Tuplable(&())
            }
        }

        impl fmt::Debug for Value<'_> {
            fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
                use Value::*;

                // Doc comments are expanded into the branch arms, which results
                // in a warning. It isn't a big deal, so silence it.
                #[allow(unused_doc_comments)]
                match self {
                    $(
                        $(#[$attrs])*
                        $variant(v) => fmt::Debug::fmt(v, fmt),
                    )*
                    Unit => ().fmt(fmt),
                }
            }
        }
    }
}

value! {
    /// A Rust `bool` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::Bool(true);
    /// ```
    Bool(bool),

    /// A Rust `char` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::Char('h');
    /// ```
    Char(char),

    /// A Rust `f32` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::F32(3.1415);
    /// ```
    F32(f32),

    /// A Rust `f64` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::F64(3.1415);
    /// ```
    F64(f64),

    /// A Rust `i8` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::I8(42);
    /// ```
    I8(i8),

    /// A Rust `i16` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::I16(42);
    /// ```
    I16(i16),

    /// A Rust `i32` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::I32(42);
    /// ```
    I32(i32),

    /// A Rust `i64` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::I64(42);
    /// ```
    I64(i64),

    /// A Rust `i128` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::I128(42);
    /// ```
    I128(i128),

    /// A Rust `isize` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::Isize(42);
    /// ```
    Isize(isize),

    /// A Rust `&str` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::String("hello");
    /// ```
    String(&'a str),

    /// A Rust `u8` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::U8(42);
    /// ```
    U8(u8),

    /// A Rust `u16` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::U16(42);
    /// ```
    U16(u16),

    /// A Rust `u32` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::U32(42);
    /// ```
    U32(u32),

    /// A Rust `u64` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::U64(42);
    /// ```
    U64(u64),

    /// A Rust `u128` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::U128(42);
    /// ```
    U128(u128),

    /// A Rust `usize` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let v = Value::Usize(42);
    /// ```
    Usize(usize),

    /// A Rust `&Path` value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    /// use std::path::Path;
    ///
    /// let path = Path::new("a.txt");
    /// let v = Value::Path(path);
    /// ```
    #[cfg(feature = "std")]
    Path(&'a std::path::Path),

    /// A Rust error value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    /// use std::io;
    ///
    /// let err: io::Error = io::ErrorKind::Other.into();
    /// let v = Value::Error(&err);
    /// ```
    #[cfg(feature = "std")]
    Error(&'a (dyn std::error::Error +'static)),

    /// A Rust list value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// let vals = vec![1, 2, 3, 4, 5];
    /// let v = Value::Listable(&vals);
    /// ```
    Listable(&'a dyn Listable),

    /// A Rust map value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    /// use std::collections::HashMap;
    ///
    /// let mut map = HashMap::new();
    /// map.insert("foo", 1);
    /// map.insert("bar", 2);
    ///
    /// let v = Value::Mappable(&map);
    /// ```
    Mappable(&'a dyn Mappable),

    /// A Rust struct value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::{Value, Valuable};
    ///
    /// #[derive(Valuable)]
    /// struct MyStruct {
    ///     field: u32,
    /// }
    ///
    /// let my_struct = MyStruct {
    ///     field: 123,
    /// };
    ///
    /// let v = Value::Structable(&my_struct);
    /// ```
    Structable(&'a dyn Structable),

    /// A Rust enum value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::{Value, Valuable};
    ///
    /// #[derive(Valuable)]
    /// enum MyEnum {
    ///     Foo,
    ///     Bar,
    /// }
    ///
    /// let my_enum = MyEnum::Foo;
    /// let v = Value::Enumerable(&my_enum);
    /// ```
    Enumerable(&'a dyn Enumerable),

    /// A tuple value
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::{Value, Valuable};
    ///
    /// let my_tuple = (123, 456);
    /// let v = Value::Tuplable(&my_tuple);
    /// ```
    Tuplable(&'a dyn Tuplable),
}

impl Valuable for Value<'_> {
    fn as_value(&self) -> Value<'_> {
        *self
    }

    fn visit(&self, visit: &mut dyn Visit) {
        visit.visit_value(*self);
    }
}

impl Default for Value<'_> {
    fn default() -> Self {
        Value::Unit
    }
}

macro_rules! convert {
    (
        $(
            $(#[$attrs:meta])*
            $ty:ty => $as:ident,
        )*
    ) => {
        impl<'a> Value<'a> {
            /// Return a `bool` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// assert_eq!(Value::Bool(true).as_bool(), Some(true));
            /// assert_eq!(Value::Char('c').as_bool(), None);
            /// ```
            pub fn as_bool(&self) -> Option<bool> {
                match *self {
                    Value::Bool(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `char` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// assert_eq!(Value::Char('c').as_char(), Some('c'));
            /// assert_eq!(Value::Bool(true).as_char(), None);
            /// ```
            pub fn as_char(&self) -> Option<char> {
                match *self {
                    Value::Char(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `f32` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// assert_eq!(Value::F32(3.1415).as_f32(), Some(3.1415));
            /// assert_eq!(Value::Bool(true).as_f32(), None);
            /// ```
            pub fn as_f32(&self) -> Option<f32> {
                match *self {
                    Value::F32(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `f64` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// assert_eq!(Value::F64(3.1415).as_f64(), Some(3.1415));
            /// assert_eq!(Value::Bool(true).as_f64(), None);
            /// ```
            pub fn as_f64(&self) -> Option<f64> {
                match *self {
                    Value::F64(v) => Some(v),
                    _ => None,
                }
            }

            $(
                $(#[$attrs])*
                pub fn $as(&self) -> Option<$ty> {
                    use Value::*;
                    use core::convert::TryInto;

                    match *self {
                        I8(v) => v.try_into().ok(),
                        I16(v) => v.try_into().ok(),
                        I32(v) => v.try_into().ok(),
                        I64(v) => v.try_into().ok(),
                        I128(v) => v.try_into().ok(),
                        Isize(v) => v.try_into().ok(),
                        U8(v) => v.try_into().ok(),
                        U16(v) => v.try_into().ok(),
                        U32(v) => v.try_into().ok(),
                        U64(v) => v.try_into().ok(),
                        U128(v) => v.try_into().ok(),
                        Usize(v) => v.try_into().ok(),
                        _ => None,
                    }
                }
            )*

            /// Return a `&str` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// assert_eq!(Value::String("hello").as_str(), Some("hello"));
            /// assert_eq!(Value::Bool(true).as_str(), None);
            /// ```
            pub fn as_str(&self) -> Option<&str> {
                match *self {
                    Value::String(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&Path` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            /// use std::path::Path;
            ///
            /// let path = Path::new("a.txt");
            ///
            /// assert!(Value::Path(path).as_path().is_some());
            /// assert!(Value::Bool(true).as_path().is_none());
            /// ```
            #[cfg(feature = "std")]
            pub fn as_path(&self) -> Option<&std::path::Path> {
                match *self {
                    Value::Path(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&dyn Error` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            /// use std::io;
            ///
            /// let err: io::Error = io::ErrorKind::Other.into();
            ///
            /// assert!(Value::Error(&err).as_error().is_some());
            /// assert!(Value::Bool(true).as_error().is_none());
            /// ```
            #[cfg(feature = "std")]
            pub fn as_error(&self) -> Option<&(dyn std::error::Error + 'static)> {
                match *self {
                    Value::Error(v) => Some(v),
                    _ => None,
                }
            }


            /// Return a `&dyn Listable` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// let list = vec![1, 2, 3, 4];
            ///
            /// assert!(Value::Listable(&list).as_listable().is_some());
            /// assert!(Value::Bool(true).as_listable().is_none());
            /// ```
            pub fn as_listable(&self) -> Option<&dyn Listable> {
                match *self {
                    Value::Listable(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&dyn Mappable` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            /// use std::collections::HashMap;
            ///
            /// let mut map = HashMap::new();
            /// map.insert("foo", 123);
            /// map.insert("bar", 456);
            ///
            /// assert!(Value::Mappable(&map).as_mappable().is_some());
            /// assert!(Value::Bool(true).as_mappable().is_none());
            /// ```
            pub fn as_mappable(&self) -> Option<&dyn Mappable> {
                match *self {
                    Value::Mappable(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&dyn Structable` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::{Value, Valuable};
            ///
            /// #[derive(Valuable)]
            /// struct Hello {
            ///     message: &'static str,
            /// }
            ///
            /// let hello = Hello { message: "Hello world" };
            ///
            /// assert!(Value::Structable(&hello).as_structable().is_some());
            /// assert!(Value::Bool(true).as_structable().is_none());
            /// ```
            pub fn as_structable(&self) -> Option<&dyn Structable> {
                match *self {
                    Value::Structable(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&dyn Enumerable` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::{Value, Valuable};
            ///
            /// #[derive(Valuable)]
            /// enum Greet {
            ///     Hello,
            ///     World,
            /// }
            ///
            /// let greet = Greet::Hello;
            ///
            /// assert!(Value::Enumerable(&greet).as_enumerable().is_some());
            /// assert!(Value::Bool(true).as_enumerable().is_none());
            /// ```
            pub fn as_enumerable(&self) -> Option<&dyn Enumerable> {
                match *self {
                    Value::Enumerable(v) => Some(v),
                    _ => None,
                }
            }

            /// Return a `&dyn Tuplable` representation of `self`, if possible.
            ///
            /// # Examples
            ///
            /// ```
            /// use valuable::Value;
            ///
            /// let my_tuple = (123, 456);
            ///
            /// assert!(Value::Tuplable(&my_tuple).as_tuplable().is_some());
            /// assert!(Value::Bool(true).as_tuplable().is_none());
            /// ```
            pub fn as_tuplable(&self) -> Option<&dyn Tuplable> {
                match *self {
                    Value::Tuplable(v) => Some(v),
                    _ => None,
                }
            }
        }
    }
}

convert! {
    /// Return a `i8` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::I8(42).as_i8(), Some(42));
    /// assert_eq!(Value::I32(42).as_i8(), Some(42));
    ///
    /// assert_eq!(Value::I64(i64::MAX).as_i8(), None);
    /// assert_eq!(Value::Bool(true).as_i8(), None);
    /// ```
    i8 => as_i8,

    /// Return a `i16` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::I16(42).as_i16(), Some(42));
    /// assert_eq!(Value::I32(42).as_i16(), Some(42));
    ///
    /// assert_eq!(Value::I64(i64::MAX).as_i16(), None);
    /// assert_eq!(Value::Bool(true).as_i16(), None);
    /// ```
    i16 => as_i16,

    /// Return a `i32` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::I32(42).as_i32(), Some(42));
    /// assert_eq!(Value::I64(42).as_i32(), Some(42));
    ///
    /// assert_eq!(Value::I64(i64::MAX).as_i32(), None);
    /// assert_eq!(Value::Bool(true).as_i32(), None);
    /// ```
    i32 => as_i32,

    /// Return a `i64` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::I64(42).as_i64(), Some(42));
    /// assert_eq!(Value::I128(42).as_i64(), Some(42));
    ///
    /// assert_eq!(Value::I128(i128::MAX).as_i64(), None);
    /// assert_eq!(Value::Bool(true).as_i64(), None);
    /// ```
    i64 => as_i64,

    /// Return a `i128` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::I128(42).as_i128(), Some(42));
    /// assert_eq!(Value::U128(42).as_i128(), Some(42));
    ///
    /// assert_eq!(Value::U128(u128::MAX).as_i128(), None);
    /// assert_eq!(Value::Bool(true).as_i128(), None);
    /// ```
    i128 => as_i128,

    /// Return a `isize` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::Isize(42).as_isize(), Some(42));
    /// assert_eq!(Value::Usize(42).as_isize(), Some(42));
    ///
    /// assert_eq!(Value::Usize(usize::MAX).as_isize(), None);
    /// assert_eq!(Value::Bool(true).as_isize(), None);
    /// ```
    isize => as_isize,

    /// Return a `u8` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::U8(42).as_u8(), Some(42));
    /// assert_eq!(Value::U32(42).as_u8(), Some(42));
    ///
    /// assert_eq!(Value::U32(u32::MAX).as_u8(), None);
    /// assert_eq!(Value::Bool(true).as_u8(), None);
    /// ```
    u8 => as_u8,

    /// Return a `u16` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::U16(42).as_u16(), Some(42));
    /// assert_eq!(Value::U32(42).as_u16(), Some(42));
    ///
    /// assert_eq!(Value::U32(u32::MAX).as_u16(), None);
    /// assert_eq!(Value::Bool(true).as_u16(), None);
    /// ```
    u16 => as_u16,

    /// Return a `u32` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::U32(42).as_u32(), Some(42));
    /// assert_eq!(Value::U64(42).as_u32(), Some(42));
    ///
    /// assert_eq!(Value::U64(u64::MAX).as_u32(), None);
    /// assert_eq!(Value::Bool(true).as_u32(), None);
    /// ```
    u32 => as_u32,

    /// Return a `u64` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::U64(42).as_u64(), Some(42));
    /// assert_eq!(Value::U128(42).as_u64(), Some(42));
    ///
    /// assert_eq!(Value::U128(u128::MAX).as_u64(), None);
    /// assert_eq!(Value::Bool(true).as_u64(), None);
    /// ```
    u64 => as_u64,

    /// Return a `u128` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::U128(42).as_u128(), Some(42));
    /// assert_eq!(Value::I32(42).as_u128(), Some(42));
    ///
    /// assert_eq!(Value::I32(-5).as_u128(), None);
    /// assert_eq!(Value::Bool(true).as_u128(), None);
    /// ```
    u128 => as_u128,

    /// Return a `usize` representation of `self`, if possible.
    ///
    /// # Examples
    ///
    /// ```
    /// use valuable::Value;
    ///
    /// assert_eq!(Value::Usize(42).as_usize(), Some(42));
    /// assert_eq!(Value::I8(42).as_usize(), Some(42));
    ///
    /// assert_eq!(Value::I8(-5).as_usize(), None);
    /// assert_eq!(Value::Bool(true).as_usize(), None);
    /// ```
    usize => as_usize,
}
