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

//! # Value

mod formatter;
mod impls;

use {
    alloc::{
        collections::BTreeMap,
        string::String,
        vec::Vec,
    },
    core::convert::TryFrom,
    crate::{Error, Number},
};

#[cfg(feature="std")]
use {
    core::str::FromStr,
    std::io::Write,

    crate::IoResult,

    self::formatter::*,
};

/// # Array
///
/// ## Shortcuts
///
/// <small>[`array()`], [`array_with_capacity()`], [`push()`]</small>
///
/// [`array()`]: fn.array.html
/// [`array_with_capacity()`]: fn.array_with_capacity.html
/// [`push()`]: fn.push.html
pub type Array = Vec<Value>;

/// # Object
///
/// ## Shortcuts
///
/// <small>[`object()`], [`insert()`]</small>
///
/// [`object()`]: fn.object.html
/// [`insert()`]: fn.insert.html
pub type Object = BTreeMap<ObjectKey, Value>;

/// # Object key
pub type ObjectKey = String;

/// # A value
///
/// ## Formatting
///
/// ### Formatting as JSON string
///
/// - To format as compacted JSON string, you can use [`format()`] or [`format_as_bytes()`].
/// - To format as a nice JSON string, you can use [`format_nicely()`].
///
/// ### Writing as JSON string to [`Write`][std::io/Write]
///
/// Can be done via [`write()`] or [`write_nicely()`].
///
/// ## Converting Rust types to `Value` and vice versa
///
/// There are some implementations:
///
/// ```ignore
/// impl From<...> for Value;
/// impl TryFrom<&Value> for ...;
/// impl TryFrom<Value> for ...;
/// ```
///
/// About [`TryFrom`][core::convert/TryFrom] implementations:
///
/// - For primitives, since they're cheap, they have implementations on either a borrowed or an owned value.
/// - For collections such as [`String`], [`Object`], [`Array`]..., they only have implementations on an owned value. So data is moved, not
///   copied.
///
/// ## Shortcuts
///
/// A root JSON value can be either an object or an array. For your convenience, there are some shortcuts, like below examples.
///
/// - Object:
///
///     ```
///     # #[cfg(feature="std")]
///     # fn test() -> sj::IoResult<()> {
///     use core::convert::TryFrom;
///
///     let mut object = sj::object();
///     object.insert("first", true)?;
///     object.insert("second", <Option<u8>>::None)?;
///     object.insert(String::from("third"), "...")?;
///
///     assert!(bool::try_from(object.by(&["first"])?)?);
///     assert!(object.take_by(&["second"])?.try_into_or(true)?);
///     assert_eq!(object.format()?, r#"{"first":true,"third":"..."}"#);
///     # Ok(()) }
///     # #[cfg(feature="std")]
///     # test().unwrap();
///     # Ok::<_, sj::Error>(())
///     ```
///
/// - Array:
///
///     ```
///     # #[cfg(feature="std")]
///     # fn test() -> sj::IoResult<()> {
///     use core::convert::TryFrom;
///
///     let mut array = sj::array();
///     array.push(false)?;
///     array.push("a string")?;
///     array.push(Some(sj::object()))?;
///
///     assert!(bool::try_from(array.at(&[0])?)? == false);
///     assert_eq!(array.format()?, r#"[false,"a string",{}]"#);
///     # Ok(()) }
///     # #[cfg(feature="std")]
///     # test().unwrap();
///     # Ok::<_, sj::Error>(())
///     ```
///
/// [core::convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
/// [std::io/Write]: https://doc.rust-lang.org/std/io/trait.Write.html
///
/// [`Array`]: #variant.Array
/// [`Object`]: #variant.Object
/// [`String`]: #variant.String
/// [`format()`]: #method.format
/// [`format_nicely()`]: #method.format_nicely
/// [`format_as_bytes()`]: #method.format_as_bytes
/// [`write()`]: #method.write
/// [`write_nicely()`]: #method.write_nicely
#[derive(Debug, Clone)]
pub enum Value {

    /// [_Shortcuts_](#shortcuts-for-string)
    String(String),

    /// ### Shortcuts
    ///
    /// <small>[`TryFrom`][core::convert/TryFrom]</small>
    ///
    /// [core::convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
    Number(Number),

    /// ### Shortcuts
    ///
    /// <small>[`TryFrom`][core::convert/TryFrom]</small>
    ///
    /// [core::convert/TryFrom]: https://doc.rust-lang.org/core/convert/trait.TryFrom.html
    Boolean(bool),

    /// [_Shortcuts_](#shortcuts-for-null)
    Null,

    /// [_Shortcuts_](#shortcuts-for-object)
    Object(Object),

    /// [_Shortcuts_](#shortcuts-for-array)
    Array(Array),

}

impl Value {

    /// # Formats this value as a compacted JSON string
    #[cfg(feature="std")]
    pub fn format_as_bytes(&self) -> IoResult<Vec<u8>> {
        let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, None, None));
        self.write(&mut buf)?;
        buf.flush().map(|()| buf)
    }

    /// # Nicely formats this value as JSON string
    ///
    /// If you don't provide tab size, default (`4`) will be used.
    #[cfg(feature="std")]
    pub fn format_nicely_as_bytes(&self, tab: Option<u8>) -> IoResult<Vec<u8>> {
        let mut buf = Vec::with_capacity(formatter::estimate_format_size(self, Some(tab.unwrap_or(formatter::DEFAULT_TAB_WIDTH)), None));
        self.write_nicely(tab, &mut buf)?;
        buf.flush().map(|()| buf)
    }

    /// # Formats this value as a compacted JSON string
    #[cfg(feature="std")]
    pub fn format(&self) -> IoResult<String> {
        #[allow(unsafe_code)]
        Ok(unsafe {
            String::from_utf8_unchecked(self.format_as_bytes()?)
        })
    }

    /// # Nicely formats this value as JSON string
    ///
    /// If you don't provide tab size, default (`4`) will be used.
    #[cfg(feature="std")]
    pub fn format_nicely(&self, tab: Option<u8>) -> IoResult<String> {
        #[allow(unsafe_code)]
        Ok(unsafe {
            String::from_utf8_unchecked(self.format_nicely_as_bytes(tab)?)
        })
    }

    /// # Writes this value as compacted JSON string to a stream
    ///
    /// ## Notes
    ///
    /// - The stream is used as-is. For better performance, you _should_ wrap your stream inside a [`BufWriter`][std::io/BufWriter].
    /// - This function does **not** flush the stream when done.
    ///
    /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
    #[cfg(feature="std")]
    pub fn write<W>(&self, stream: &mut W) -> IoResult<()> where W: Write {
        Formatter::new(None).format(self, stream)
    }

    /// # Writes this value as nicely formatted JSON string to a stream
    ///
    /// ## Notes
    ///
    /// - If you don't provide tab size, default (`4`) will be used.
    /// - The stream is used as-is. For better performance, you _should_ wrap your stream inside a [`BufWriter`][std::io/BufWriter].
    /// - This function does **not** flush the stream when done.
    ///
    /// [std::io/BufWriter]: https://doc.rust-lang.org/std/io/struct.BufWriter.html
    #[cfg(feature="std")]
    pub fn write_nicely<W>(&self, tab: Option<u8>, stream: &mut W) -> IoResult<()> where W: Write {
        let tab = match tab {
            Some(_) => tab,
            None => Some(formatter::DEFAULT_TAB_WIDTH),
        };
        Formatter::new(tab).format(self, stream)
    }

}

impl From<Number> for Value {

    fn from(n: Number) -> Self {
        Value::Number(n)
    }

}

impl From<bool> for Value {

    fn from(b: bool) -> Self {
        Value::Boolean(b)
    }

}

impl TryFrom<&Value> for bool {

    type Error = Error;

    fn try_from(value: &Value) -> Result<Self, Self::Error> {
        match value {
            Value::Boolean(b) => Ok(*b),
            _ => Err(Error::from(__!("Value is not a Boolean"))),
        }
    }

}

impl TryFrom<Value> for bool {

    type Error = Error;

    fn try_from(value: Value) -> Result<Self, Self::Error> {
        Self::try_from(&value)
    }

}

impl<T> From<Option<T>> for Value where T: Into<Value> {

    fn from(t: Option<T>) -> Self {
        match t {
            Some(t) => t.into(),
            None => Value::Null,
        }
    }

}

#[cfg(feature="std")]
impl FromStr for Value {

    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        crate::parse_bytes(s)
    }

}

macro_rules! impl_try_from_value_for_primitives { ($($ty: ty,)+) => {
    $(
        impl TryFrom<&Value> for $ty {

            type Error = Error;

            fn try_from(value: &Value) -> Result<Self, Self::Error> {
                match value {
                    Value::Number(n) => Self::try_from(n),
                    _ => Err(Error::from(__!("Value is not a Number"))),
                }
            }

        }

        impl TryFrom<Value> for $ty {

            type Error = Error;

            fn try_from(value: Value) -> Result<Self, Self::Error> {
                Self::try_from(&value)
            }

        }
    )+
}}

impl_try_from_value_for_primitives! {
    i8, i16, i32, i64, i128, isize,
    u8, u16, u32, u64, u128, usize,
    f32, f64,
}

#[cfg(feature="std")]
impl TryFrom<Vec<u8>> for Value {

    type Error = Error;

    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
        crate::parse_bytes(bytes)
    }

}

/// # Makes new object
pub fn object() -> Value {
    Value::Object(Object::new())
}

/// # Makes new array
pub fn array() -> Value {
    Value::Array(Vec::new())
}

/// # Makes new array with capacity
pub fn array_with_capacity(capacity: usize) -> Value {
    Value::Array(Vec::with_capacity(capacity))
}

/// # Pushes new item into an array
pub fn push<T>(array: &mut Array, value: T) where T: Into<Value> {
    array.push(value.into());
}

/// # Inserts new item into an object
///
/// Returns previous value (if it existed).
pub fn insert<K, V>(object: &mut Object, key: K, value: V) -> Option<Value> where K: Into<ObjectKey>, V: Into<Value> {
    object.insert(key.into(), value.into())
}
