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

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

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

    crate::{Array, Error, Result, Value},
};

/// # Helper macro for Value::at()/mut_at()
macro_rules! at_or_mut_at { ($self: ident, $indexes: ident, $code: tt) => {{
    let mut value = Some($self);
    for (nth, idx) in $indexes.iter().enumerate() {
        match value {
            Some(Value::Array(array)) => {
                value = array.$code(*idx);
                if nth + 1 == $indexes.len() {
                    return value.ok_or_else(|| Error::from(__!("Indexes are invalid: {:?}", $indexes)));
                }
            },
            Some(_) => return Err(Error::from(match nth {
                0 => __!("Value is not an Array"),
                _ => __!("Value at {:?} is not an Array", &$indexes[..nth]),
            })),
            None => return Err(Error::from(__!("There is no value at {:?}", &$indexes[..nth]))),
        };
    }

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

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

    /// # If the value is an array, pushes new item into it
    ///
    /// Returns an error if the value is not an array.
    pub fn push<T>(&mut self, value: T) -> Result<()> where T: Into<Self> {
        match self {
            Value::Array(array) => Ok(crate::push(array, value)),
            _ => Err(Error::from(__!("Value is not an Array"))),
        }
    }

    /// # Gets an immutable item from this array and its sub arrays
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Indexes are empty or invalid.
    /// - The value or any of its sub items is not an array.
    ///
    /// ## Examples
    ///
    /// ```
    /// let mut array = sj::array();
    /// array.push("first")?;
    /// array.push(vec![false.into(), "second".into()])?;
    ///
    /// assert_eq!(array.at(&[0])?.as_str()?, "first");
    /// assert_eq!(array.at(&[1, 1])?.as_str()?, "second");
    ///
    /// assert!(array.at(&[]).is_err());
    /// assert!(array.at(&[0, 1]).is_err());
    /// assert!(array.at(&[1, 2]).is_err());
    /// assert!(array.at(&[1, 0, 0]).is_err());
    /// assert!(array.at(&[1, 1, 2]).is_err());
    ///
    /// # Ok::<_, sj::Error>(())
    /// ```
    pub fn at(&self, indexes: &[usize]) -> Result<&Self> {
        at_or_mut_at!(self, indexes, get)
    }

    /// # Gets a mutable item from this array and its sub arrays
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Indexes are empty or invalid.
    /// - The value or any of its sub items is not an array.
    ///
    /// ## See also
    ///
    /// [`at()`][at()]
    ///
    /// [at()]: #method.at
    pub fn mut_at(&mut self, indexes: &[usize]) -> Result<&mut Self> {
        at_or_mut_at!(self, indexes, get_mut)
    }

    /// # Takes an item from this array and its sub arrays
    ///
    /// The function returns an error on one of these conditions:
    ///
    /// - Indexes are empty or invalid.
    /// - The value or any of its sub items is not an array.
    ///
    /// ## Examples
    ///
    /// ```
    /// let mut array = sj::array();
    /// array.push("earth")?;
    /// array.push(vec![false.into(), "moon".into()])?;
    ///
    /// assert_eq!(array.take_at(&[0])?.as_str()?, "earth");
    /// assert_eq!(array.take_at(&[0, 1])?.as_str()?, "moon");
    ///
    /// assert!(array.take_at(&[]).is_err());
    /// assert!(array.take_at(&[0, 1]).is_err());
    ///
    /// # Ok::<_, sj::Error>(())
    /// ```
    pub fn take_at(&mut self, indexes: &[usize]) -> Result<Self> {
        let mut value = Some(self);
        for (nth, idx) in indexes.iter().enumerate() {
            match value {
                Some(Value::Array(array)) => match nth + 1 == indexes.len() {
                    true => return match idx >= &0 && idx < &array.len() {
                        true => Ok(array.remove(*idx)),
                        false => Err(Error::from(__!("Invalid indexes: {:?}", indexes))),
                    },
                    false => value = array.get_mut(*idx),
                },
                Some(_) => return Err(Error::from(match nth {
                    0 => __!("Value is not an Array"),
                    _ => __!("Value at {:?} is not an Array", &indexes[..nth]),
                })),
                None => return Err(Error::from(__!("There is no value at {:?}", &indexes[..nth]))),
            };
        }

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

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

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

}

impl From<Array> for Value {

    fn from(values: Array) -> Self {
        Value::Array(values)
    }

}

impl FromIterator<Value> for Value {

    fn from_iter<T>(iter: T) -> Self where T: IntoIterator<Item=Self> {
        Value::Array(iter.into_iter().collect())
    }

}

impl TryFrom<Value> for Array {

    type Error = Error;

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

}
