use std::convert::TryInto;
use std::fmt;
use std::fmt::Display;

use serde::de::{Deserialize, Deserializer, Visitor};

use crate::error::{ConfigError, Result, Unexpected};
use crate::map::Map;

/// Underlying kind of the configuration value.
///
/// Standard operations on a `Value` by users of this crate do not require
/// knowledge of `ValueKind`. Introspection of underlying kind is only required
/// when the configuration values are unstructured or do not have known types.
#[derive(Debug, Clone, PartialEq)]
pub enum ValueKind {
    Nil,
    Boolean(bool),
    I64(i64),
    I128(i128),
    U64(u64),
    U128(u128),
    Float(f64),
    String(String),
    Table(Table),
    Array(Array),
}

pub type Array = Vec<Value>;
pub type Table = Map<String, Value>;

impl Default for ValueKind {
    fn default() -> Self {
        Self::Nil
    }
}

impl<T> From<Option<T>> for ValueKind
where
    T: Into<Self>,
{
    fn from(value: Option<T>) -> Self {
        match value {
            Some(value) => value.into(),
            None => Self::Nil,
        }
    }
}

impl From<String> for ValueKind {
    fn from(value: String) -> Self {
        Self::String(value)
    }
}

impl<'a> From<&'a str> for ValueKind {
    fn from(value: &'a str) -> Self {
        Self::String(value.into())
    }
}

impl From<i8> for ValueKind {
    fn from(value: i8) -> Self {
        Self::I64(value.into())
    }
}

impl From<i16> for ValueKind {
    fn from(value: i16) -> Self {
        Self::I64(value.into())
    }
}

impl From<i32> for ValueKind {
    fn from(value: i32) -> Self {
        Self::I64(value.into())
    }
}

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

impl From<i128> for ValueKind {
    fn from(value: i128) -> Self {
        Self::I128(value)
    }
}

impl From<u8> for ValueKind {
    fn from(value: u8) -> Self {
        Self::U64(value.into())
    }
}

impl From<u16> for ValueKind {
    fn from(value: u16) -> Self {
        Self::U64(value.into())
    }
}

impl From<u32> for ValueKind {
    fn from(value: u32) -> Self {
        Self::U64(value.into())
    }
}

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

impl From<u128> for ValueKind {
    fn from(value: u128) -> Self {
        Self::U128(value)
    }
}

impl From<f64> for ValueKind {
    fn from(value: f64) -> Self {
        Self::Float(value)
    }
}

impl From<bool> for ValueKind {
    fn from(value: bool) -> Self {
        Self::Boolean(value)
    }
}

impl<T> From<Map<String, T>> for ValueKind
where
    T: Into<Value>,
{
    fn from(values: Map<String, T>) -> Self {
        let t = values.into_iter().map(|(k, v)| (k, v.into())).collect();
        Self::Table(t)
    }
}

impl<T> From<Vec<T>> for ValueKind
where
    T: Into<Value>,
{
    fn from(values: Vec<T>) -> Self {
        Self::Array(values.into_iter().map(T::into).collect())
    }
}

impl Display for ValueKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Self::String(ref value) => write!(f, "{}", value),
            Self::Boolean(value) => write!(f, "{}", value),
            Self::I64(value) => write!(f, "{}", value),
            Self::I128(value) => write!(f, "{}", value),
            Self::U64(value) => write!(f, "{}", value),
            Self::U128(value) => write!(f, "{}", value),
            Self::Float(value) => write!(f, "{}", value),
            Self::Nil => write!(f, "nil"),
            Self::Table(ref table) => write!(f, "{{ {} }}", {
                table
                    .iter()
                    .map(|(k, v)| format!("{} => {}, ", k, v))
                    .collect::<String>()
            }),
            Self::Array(ref array) => write!(f, "{:?}", {
                array.iter().map(|e| format!("{}, ", e)).collect::<String>()
            }),
        }
    }
}

/// A configuration value.
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Value {
    /// A description of the original location of the value.
    ///
    /// A Value originating from a File might contain:
    /// ```text
    /// Settings.toml
    /// ```
    ///
    /// A Value originating from the environment would contain:
    /// ```text
    /// the envrionment
    /// ```
    ///
    /// A Value originating from a remote source might contain:
    /// ```text
    /// etcd+http://127.0.0.1:2379
    /// ```
    origin: Option<String>,

    /// Underlying kind of the configuration value.
    pub kind: ValueKind,
}

impl Value {
    /// Create a new value instance that will remember its source uri.
    pub fn new<V>(origin: Option<&String>, kind: V) -> Self
    where
        V: Into<ValueKind>,
    {
        Self {
            origin: origin.cloned(),
            kind: kind.into(),
        }
    }

    /// Attempt to deserialize this value into the requested type.
    pub fn try_deserialize<'de, T: Deserialize<'de>>(self) -> Result<T> {
        T::deserialize(self)
    }

    /// Returns `self` as a bool, if possible.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_bool(self) -> Result<bool> {
        match self.kind {
            ValueKind::Boolean(value) => Ok(value),
            ValueKind::I64(value) => Ok(value != 0),
            ValueKind::I128(value) => Ok(value != 0),
            ValueKind::U64(value) => Ok(value != 0),
            ValueKind::U128(value) => Ok(value != 0),
            ValueKind::Float(value) => Ok(value != 0.0),

            ValueKind::String(ref value) => {
                match value.to_lowercase().as_ref() {
                    "1" | "true" | "on" | "yes" => Ok(true),
                    "0" | "false" | "off" | "no" => Ok(false),

                    // Unexpected string value
                    s => Err(ConfigError::invalid_type(
                        self.origin.clone(),
                        Unexpected::Str(s.into()),
                        "a boolean",
                    )),
                }
            }

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "a boolean",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "a boolean",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "a boolean",
            )),
        }
    }

    /// Returns `self` into an i64, if possible.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_int(self) -> Result<i64> {
        match self.kind {
            ValueKind::I64(value) => Ok(value),
            ValueKind::I128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::I128(value),
                    "an signed 64 bit or less integer",
                )
            }),
            ValueKind::U64(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::U64(value),
                    "an signed 64 bit or less integer",
                )
            }),
            ValueKind::U128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::U128(value),
                    "an signed 64 bit or less integer",
                )
            }),

            ValueKind::String(ref s) => {
                match s.to_lowercase().as_ref() {
                    "true" | "on" | "yes" => Ok(1),
                    "false" | "off" | "no" => Ok(0),
                    _ => {
                        s.parse().map_err(|_| {
                            // Unexpected string
                            ConfigError::invalid_type(
                                self.origin.clone(),
                                Unexpected::Str(s.clone()),
                                "an integer",
                            )
                        })
                    }
                }
            }

            ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }),
            ValueKind::Float(value) => Ok(value.round() as i64),

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "an integer",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "an integer",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "an integer",
            )),
        }
    }

    /// Returns `self` into an i128, if possible.
    pub fn into_int128(self) -> Result<i128> {
        match self.kind {
            ValueKind::I64(value) => Ok(value.into()),
            ValueKind::I128(value) => Ok(value),
            ValueKind::U64(value) => Ok(value.into()),
            ValueKind::U128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::U128(value),
                    "an signed 128 bit integer",
                )
            }),

            ValueKind::String(ref s) => {
                match s.to_lowercase().as_ref() {
                    "true" | "on" | "yes" => Ok(1),
                    "false" | "off" | "no" => Ok(0),
                    _ => {
                        s.parse().map_err(|_| {
                            // Unexpected string
                            ConfigError::invalid_type(
                                self.origin.clone(),
                                Unexpected::Str(s.clone()),
                                "an integer",
                            )
                        })
                    }
                }
            }

            ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }),
            ValueKind::Float(value) => Ok(value.round() as i128),

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "an integer",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "an integer",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "an integer",
            )),
        }
    }

    /// Returns `self` into an u64, if possible.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_uint(self) -> Result<u64> {
        match self.kind {
            ValueKind::U64(value) => Ok(value),
            ValueKind::U128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::U128(value),
                    "an unsigned 64 bit or less integer",
                )
            }),
            ValueKind::I64(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::I64(value),
                    "an unsigned 64 bit or less integer",
                )
            }),
            ValueKind::I128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::I128(value),
                    "an unsigned 64 bit or less integer",
                )
            }),

            ValueKind::String(ref s) => {
                match s.to_lowercase().as_ref() {
                    "true" | "on" | "yes" => Ok(1),
                    "false" | "off" | "no" => Ok(0),
                    _ => {
                        s.parse().map_err(|_| {
                            // Unexpected string
                            ConfigError::invalid_type(
                                self.origin.clone(),
                                Unexpected::Str(s.clone()),
                                "an integer",
                            )
                        })
                    }
                }
            }

            ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }),
            ValueKind::Float(value) => Ok(value.round() as u64),

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "an integer",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "an integer",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "an integer",
            )),
        }
    }

    /// Returns `self` into an u128, if possible.
    pub fn into_uint128(self) -> Result<u128> {
        match self.kind {
            ValueKind::U64(value) => Ok(value.into()),
            ValueKind::U128(value) => Ok(value),
            ValueKind::I64(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::I64(value),
                    "an unsigned 128 bit or less integer",
                )
            }),
            ValueKind::I128(value) => value.try_into().map_err(|_| {
                ConfigError::invalid_type(
                    self.origin,
                    Unexpected::I128(value),
                    "an unsigned 128 bit or less integer",
                )
            }),

            ValueKind::String(ref s) => {
                match s.to_lowercase().as_ref() {
                    "true" | "on" | "yes" => Ok(1),
                    "false" | "off" | "no" => Ok(0),
                    _ => {
                        s.parse().map_err(|_| {
                            // Unexpected string
                            ConfigError::invalid_type(
                                self.origin.clone(),
                                Unexpected::Str(s.clone()),
                                "an integer",
                            )
                        })
                    }
                }
            }

            ValueKind::Boolean(value) => Ok(if value { 1 } else { 0 }),
            ValueKind::Float(value) => Ok(value.round() as u128),

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "an integer",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "an integer",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "an integer",
            )),
        }
    }

    /// Returns `self` into a f64, if possible.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_float(self) -> Result<f64> {
        match self.kind {
            ValueKind::Float(value) => Ok(value),

            ValueKind::String(ref s) => {
                match s.to_lowercase().as_ref() {
                    "true" | "on" | "yes" => Ok(1.0),
                    "false" | "off" | "no" => Ok(0.0),
                    _ => {
                        s.parse().map_err(|_| {
                            // Unexpected string
                            ConfigError::invalid_type(
                                self.origin.clone(),
                                Unexpected::Str(s.clone()),
                                "a floating point",
                            )
                        })
                    }
                }
            }

            ValueKind::I64(value) => Ok(value as f64),
            ValueKind::I128(value) => Ok(value as f64),
            ValueKind::U64(value) => Ok(value as f64),
            ValueKind::U128(value) => Ok(value as f64),
            ValueKind::Boolean(value) => Ok(if value { 1.0 } else { 0.0 }),

            // Unexpected type
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "a floating point",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "a floating point",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "a floating point",
            )),
        }
    }

    /// Returns `self` into a string, if possible.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_string(self) -> Result<String> {
        match self.kind {
            ValueKind::String(value) => Ok(value),

            ValueKind::Boolean(value) => Ok(value.to_string()),
            ValueKind::I64(value) => Ok(value.to_string()),
            ValueKind::I128(value) => Ok(value.to_string()),
            ValueKind::U64(value) => Ok(value.to_string()),
            ValueKind::U128(value) => Ok(value.to_string()),
            ValueKind::Float(value) => Ok(value.to_string()),

            // Cannot convert
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "a string",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "a string",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "a string",
            )),
        }
    }

    /// Returns `self` into an array, if possible
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_array(self) -> Result<Vec<Self>> {
        match self.kind {
            ValueKind::Array(value) => Ok(value),

            // Cannot convert
            ValueKind::Float(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Float(value),
                "an array",
            )),
            ValueKind::String(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Str(value),
                "an array",
            )),
            ValueKind::I64(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::I64(value),
                "an array",
            )),
            ValueKind::I128(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::I128(value),
                "an array",
            )),
            ValueKind::U64(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::U64(value),
                "an array",
            )),
            ValueKind::U128(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::U128(value),
                "an array",
            )),
            ValueKind::Boolean(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Bool(value),
                "an array",
            )),
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "an array",
            )),
            ValueKind::Table(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Map,
                "an array",
            )),
        }
    }

    /// If the `Value` is a Table, returns the associated Map.
    // FIXME: Should this not be `try_into_*` ?
    pub fn into_table(self) -> Result<Map<String, Self>> {
        match self.kind {
            ValueKind::Table(value) => Ok(value),

            // Cannot convert
            ValueKind::Float(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Float(value),
                "a map",
            )),
            ValueKind::String(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Str(value),
                "a map",
            )),
            ValueKind::I64(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::I64(value),
                "a map",
            )),
            ValueKind::I128(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::I128(value),
                "a map",
            )),
            ValueKind::U64(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::U64(value),
                "a map",
            )),
            ValueKind::U128(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::U128(value),
                "a map",
            )),
            ValueKind::Boolean(value) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Bool(value),
                "a map",
            )),
            ValueKind::Nil => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Unit,
                "a map",
            )),
            ValueKind::Array(_) => Err(ConfigError::invalid_type(
                self.origin,
                Unexpected::Seq,
                "a map",
            )),
        }
    }
}

impl<'de> Deserialize<'de> for Value {
    #[inline]
    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct ValueVisitor;

        impl<'de> Visitor<'de> for ValueVisitor {
            type Value = Value;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("any valid configuration value")
            }

            #[inline]
            fn visit_bool<E>(self, value: bool) -> ::std::result::Result<Value, E> {
                Ok(value.into())
            }

            #[inline]
            fn visit_i8<E>(self, value: i8) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_i16<E>(self, value: i16) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_i32<E>(self, value: i32) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_i64<E>(self, value: i64) -> ::std::result::Result<Value, E> {
                Ok(value.into())
            }

            #[inline]
            fn visit_i128<E>(self, value: i128) -> ::std::result::Result<Value, E> {
                Ok(value.into())
            }

            #[inline]
            fn visit_u8<E>(self, value: u8) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_u16<E>(self, value: u16) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_u32<E>(self, value: u32) -> ::std::result::Result<Value, E> {
                Ok((i64::from(value)).into())
            }

            #[inline]
            fn visit_u64<E>(self, value: u64) -> ::std::result::Result<Value, E> {
                // FIXME: This is bad
                Ok((value as i64).into())
            }

            #[inline]
            fn visit_u128<E>(self, value: u128) -> ::std::result::Result<Value, E> {
                // FIXME: This is bad
                Ok((value as i128).into())
            }

            #[inline]
            fn visit_f64<E>(self, value: f64) -> ::std::result::Result<Value, E> {
                Ok(value.into())
            }

            #[inline]
            fn visit_str<E>(self, value: &str) -> ::std::result::Result<Value, E>
            where
                E: ::serde::de::Error,
            {
                self.visit_string(String::from(value))
            }

            #[inline]
            fn visit_string<E>(self, value: String) -> ::std::result::Result<Value, E> {
                Ok(value.into())
            }

            #[inline]
            fn visit_none<E>(self) -> ::std::result::Result<Value, E> {
                Ok(Value::new(None, ValueKind::Nil))
            }

            #[inline]
            fn visit_some<D>(self, deserializer: D) -> ::std::result::Result<Value, D::Error>
            where
                D: Deserializer<'de>,
            {
                Deserialize::deserialize(deserializer)
            }

            #[inline]
            fn visit_unit<E>(self) -> ::std::result::Result<Value, E> {
                Ok(Value::new(None, ValueKind::Nil))
            }

            #[inline]
            fn visit_seq<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error>
            where
                V: ::serde::de::SeqAccess<'de>,
            {
                let mut vec = Array::new();

                while let Some(elem) = visitor.next_element()? {
                    vec.push(elem);
                }

                Ok(vec.into())
            }

            fn visit_map<V>(self, mut visitor: V) -> ::std::result::Result<Value, V::Error>
            where
                V: ::serde::de::MapAccess<'de>,
            {
                let mut values = Table::new();

                while let Some((key, value)) = visitor.next_entry()? {
                    values.insert(key, value);
                }

                Ok(values.into())
            }
        }

        deserializer.deserialize_any(ValueVisitor)
    }
}

impl<T> From<T> for Value
where
    T: Into<ValueKind>,
{
    fn from(value: T) -> Self {
        Self {
            origin: None,
            kind: value.into(),
        }
    }
}

impl Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.kind)
    }
}

#[cfg(test)]
mod tests {
    use super::ValueKind;
    use crate::Config;
    use crate::File;
    use crate::FileFormat;

    #[test]
    fn test_i64() {
        let c = Config::builder()
            .add_source(File::new("tests/types/i64.toml", FileFormat::Toml))
            .build()
            .unwrap();

        assert!(std::matches!(c.cache.kind, ValueKind::Table(_)));
        let v = match c.cache.kind {
            ValueKind::Table(t) => t,
            _ => unreachable!(),
        };

        let value = v.get("value").unwrap();
        assert!(
            std::matches!(value.kind, ValueKind::I64(120)),
            "Is not a i64(120): {:?}",
            value.kind
        );
    }
}
