// License: see LICENSE file at root directory of `master` branch

//! # Shortcuts for `Value::Object`

use {
    core::{
        convert::TryFrom,
        iter::FromIterator,
    },

    crate::{Error, Object, ObjectKey, Result, Value},
};

/// # Helper macro for Value::maybe_by()/maybe_mut_by()
macro_rules! maybe_by_or_mut_by { ($self: ident, $keys: ident, $code: tt) => {{
    if $keys.is_empty() {
        return Err(Error::from(__!("Keys must not be empty")));
    }

    let mut value = Some($self);
    for (nth, key) in $keys.iter().enumerate() {
        match value {
            Some(Value::Object(object)) => value = object.$code(*key),
            Some(_) => return Err(Error::from(match nth {
                0 => __!("Value is not an Object"),
                _ => __!("Value at {:?} is not an Object", &$keys[..nth]),
            })),
            None => return Err(Error::from(__!("There is no value at {:?}", &$keys[..nth]))),
        };
    }

    Ok(value)
}}}

/// # Shortcuts for [`Object`](#variant.Object)
impl Value {

    /// # If the value is an object, inserts new item into it
    ///
    /// On success, returns previous value (if it existed).
    ///
    /// Returns an error if the value is not an object.
    pub fn insert<K, V>(&mut self, key: K, value: V) -> Result<Option<Self>> where K: Into<ObjectKey>, V: Into<Self> {
        match self {
            Value::Object(object) => Ok(crate::insert(object, key, value)),
            _ => Err(Error::from(__!("Value is not an Object"))),
        }
    }

    /// # Gets an immutable item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    ///
    /// ## Examples
    ///
    /// ```
    /// use core::convert::TryFrom;
    ///
    /// let mut object = sj::object();
    /// object.insert("first", true)?;
    /// object.insert("second", {
    ///     let mut map = sj::Object::new();
    ///     sj::insert(&mut map, "zero", 0);
    ///     map
    /// })?;
    ///
    /// assert_eq!(bool::try_from(object.by(&["first"])?)?, true);
    /// assert_eq!(u8::try_from(object.by(&["second", "zero"])?)?, 0);
    ///
    /// assert!(object.by(&["something"]).is_err());
    /// assert!(object.maybe_by(&["something"])?.is_none());
    ///
    /// assert!(object.by(&[]).is_err());
    /// assert!(object.by(&["first", "last"]).is_err());
    /// assert!(object.by(&["second", "third", "fourth"]).is_err());
    ///
    /// # Ok::<_, sj::Error>(())
    /// ```
    pub fn by(&self, keys: &[&str]) -> Result<&Self> {
        self.maybe_by(keys)?.ok_or_else(|| Error::from(__!("There is no value at: {:?}", keys)))
    }

    /// # Gets an optional immutable item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    pub fn maybe_by(&self, keys: &[&str]) -> Result<Option<&Self>> {
        maybe_by_or_mut_by!(self, keys, get)
    }

    /// # Gets a mutable item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    pub fn mut_by(&mut self, keys: &[&str]) -> Result<&mut Self> {
        self.maybe_mut_by(keys)?.ok_or_else(|| Error::from(__!("There is no value at: {:?}", keys)))
    }

    /// # Gets an optional mutable item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    pub fn maybe_mut_by(&mut self, keys: &[&str]) -> Result<Option<&mut Self>> {
        maybe_by_or_mut_by!(self, keys, get_mut)
    }

    /// # Takes an item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    ///
    /// ## Examples
    ///
    /// ```
    /// let mut object = sj::object();
    /// object.insert("earth", "moon")?;
    /// object.insert("solar-system", {
    ///     let mut map = sj::Object::new();
    ///     sj::insert(&mut map, "sun", "earth");
    ///     map
    /// })?;
    ///
    /// assert_eq!(object.take_by(&["earth"])?.as_str()?, "moon");
    /// assert_eq!(object.take_by(&["solar-system", "sun"])?.as_str()?, "earth");
    ///
    /// assert!(object.take_by(&["milky-way"]).is_err());
    /// assert!(object.maybe_take_by(&["milky-way"])?.is_none());
    /// assert!(object.maybe_take_by(&["solar-system", "mars"])?.is_none());
    ///
    /// assert!(object.take_by(&[]).is_err());
    /// assert!(object.take_by(&["jupiter", "venus"]).is_err());
    ///
    /// # Ok::<_, sj::Error>(())
    /// ```
    pub fn take_by(&mut self, keys: &[&str]) -> Result<Self> {
        self.maybe_take_by(keys)?.ok_or_else(|| Error::from(__!("There is no value at: {:?}", keys)))
    }

    /// # Takes an optional item from this object and its sub objects
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Keys are empty.
    /// - The value or any of its sub items is not an object.
    pub fn maybe_take_by(&mut self, keys: &[&str]) -> Result<Option<Self>> {
        let mut value = Some(self);
        for (nth, key) in keys.iter().enumerate() {
            match value {
                Some(Value::Object(object)) => match nth + 1 == keys.len() {
                    true => return Ok(object.remove(*key)),
                    false => value = object.get_mut(*key),
                },
                Some(_) => return Err(Error::from(match nth {
                    0 => __!("Value is not an Object"),
                    _ => __!("Value at {:?} is not an Object", &keys[..nth]),
                })),
                None => return Err(Error::from(__!("There is no value at {:?}", &keys[..nth]))),
            };
        }

        Err(Error::from(__!("Keys must not be empty")))
    }

    /// # If the value is an object, returns an immutable reference of it
    ///
    /// Returns an error if the value is not an object.
    pub fn as_object(&self) -> Result<&Object> {
        match self {
            Value::Object(object) => Ok(object),
            _ => Err(Error::from(__!("Value is not an Object"))),
        }
    }

    /// # If the value is an object, returns a mutable reference of it
    ///
    /// Returns an error if the value is not an object.
    pub fn as_mut_object(&mut self) -> Result<&mut Object> {
        match self {
            Value::Object(object) => Ok(object),
            _ => Err(Error::from(__!("Value is not an Object"))),
        }
    }

}

impl From<Object> for Value {

    fn from(map: Object) -> Self {
        Value::Object(map)
    }

}

impl<K, V> From<(K, V)> for Value where K: Into<ObjectKey>, V: Into<Value> {

    fn from((key, value): (K, V)) -> Self {
        let mut object = Object::new();
        crate::insert(&mut object, key, value);
        Self::from(object)
    }

}

impl FromIterator<(ObjectKey, Value)> for Value {

    fn from_iter<I>(iter: I) -> Self where I: IntoIterator<Item=(ObjectKey, Self)> {
        Value::Object(iter.into_iter().collect())
    }

}

impl TryFrom<Value> for Object {

    type Error = Error;

    fn try_from(value: Value) -> core::result::Result<Self, Self::Error> {
        match value {
            Value::Object(object) => Ok(object),
            _ => Err(Error::from(__!("Value is not an Object"))),
        }
    }

}
