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

//! # Number

mod impls;
mod tests;

use {
    core::{
        convert::TryFrom,
        fmt::{self, Display, Formatter},
    },

    crate::Error,
};

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

    crate::IoResult,
};

/// # Max integer digits
///
/// _For internal use only._
#[cfg(feature="std")]
#[cfg(any(
    target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64",
    target_pointer_width = "128",
))]
pub const MAX_INT_DIGITS: usize = 40;

/// # Max integer digits
///
/// _For internal use only._
///
/// This is redundant. But, on this big machine, some large buffer like this is fine.
#[cfg(feature="std")]
#[cfg(not(any(
    target_pointer_width = "8", target_pointer_width = "16", target_pointer_width = "32", target_pointer_width = "64",
    target_pointer_width = "128",
)))]
pub const MAX_INT_DIGITS: usize = core::mem::size_of::<usize>() * 3;

#[cfg(feature="std")]
#[test]
fn tests() {
    use alloc::string::ToString;

    assert!(MAX_INT_DIGITS >= u128::max_value().to_string().len());
    assert!(MAX_INT_DIGITS >= i128::min_value().to_string().len());

    assert!(MAX_INT_DIGITS >= usize::max_value().to_string().len());
    assert!(MAX_INT_DIGITS >= isize::min_value().to_string().len());
}

/// # Number
///
/// You rarely use this directly, but [`Value`][::Value].
///
/// ## Notes
///
/// - Internally, this struct uses an enum to store values of different types (such as `i64`, `f64`...). Since different number types cannot
///   be compared directly, [`PartialEq`][core::cmp/PartialEq] and [`PartialOrd`][core::cmp/PartialOrd] are not implemented.
/// - [`Hash`][core::hash/Hash] is not implemented. Because [`f32`][f32] and [`f64`][f64] don't implement it.
///
/// [::Value]: enum.Value.html
///
/// [f32]: https://doc.rust-lang.org/std/primitive.f32.html
/// [f64]: https://doc.rust-lang.org/std/primitive.f64.html
/// [core::cmp/PartialEq]: https://doc.rust-lang.org/core/cmp/trait.PartialEq.html
/// [core::cmp/PartialOrd]: https://doc.rust-lang.org/core/cmp/trait.PartialOrd.html
/// [core::hash/Hash]: https://doc.rust-lang.org/core/hash/trait.Hash.html
#[derive(Debug, Clone)]
pub struct Number {
    inner: Inner,
}

impl Number {

    /// # Writes this number to a stream
    #[cfg(feature="std")]
    pub(crate) fn write<W>(&self, stream: &mut W) -> IoResult<()> where W: Write {
        match self.inner {
            Inner::I32(i) => write!(stream, concat!('{', '}'), i),
            Inner::I64(i) => write!(stream, concat!('{', '}'), i),
            Inner::I128(i) => write!(stream, concat!('{', '}'), i),
            Inner::ISize(i) => write!(stream, concat!('{', '}'), i),

            Inner::U32(u) => write!(stream, concat!('{', '}'), u),
            Inner::U64(u) => write!(stream, concat!('{', '}'), u),
            Inner::U128(u) => write!(stream, concat!('{', '}'), u),
            Inner::USize(u) => write!(stream, concat!('{', '}'), u),

            Inner::F32(f) => write!(stream, concat!('{', '}'), f),
            Inner::F64(f) => write!(stream, concat!('{', '}'), f),
        }
    }

}

/// # Inner value of `Number`
///
/// This helps prevent misuses, because JSON numbers have no type.
///
/// If we expose this enum, the user may incorrectly use `match` or `if let` on a **parsed** `Value::Number(...)`. For example, they may expect
/// some ID field to be a `u64`, while the parser could have parsed it as a `u8`.
#[derive(Debug, Clone)]
enum Inner {
    I32(i32), I64(i64), I128(i128), ISize(isize),
    U32(u32), U64(u64), U128(u128), USize(usize),
    F32(f32), F64(f64),
}

impl Display for Number {

    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        match self.inner {
            Inner::I32(i) => i.fmt(f),
            Inner::U32(u) => u.fmt(f),

            Inner::I64(i) => i.fmt(f),
            Inner::U64(u) => u.fmt(f),

            Inner::I128(i) => i.fmt(f),
            Inner::U128(u) => u.fmt(f),

            Inner::ISize(i) => i.fmt(f),
            Inner::USize(u) => u.fmt(f),

            Inner::F32(x) => x.fmt(f),
            Inner::F64(x) => x.fmt(f),
        }
    }

}

macro_rules! impl_from_small_ints_for_number { ($($ty: ty, $code: tt,)+) => {
    $(
        impl From<$ty> for Number {

            fn from(n: $ty) -> Self {
                Self {
                    inner: Inner::$code(n.into()),
                }
            }

        }
    )+
}}

impl_from_small_ints_for_number! {
    i8, I32, i16, I32,
    u8, U32, u16, U32,
}

macro_rules! impl_from_primitives_for_number { ($($ty: ty, $code: tt,)+) => {
    $(
        impl From<$ty> for Number {

            fn from(n: $ty) -> Self {
                Self {
                    inner: Inner::$code(n),
                }
            }

        }
    )+
}}

impl_from_primitives_for_number! {
    i32, I32, i64, I64, i128, I128, isize, ISize,
    u32, U32, u64, U64, u128, U128, usize, USize,
    f32, F32, f64, F64,
}

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

            type Error = Error;

            fn try_from(n: &Number) -> Result<Self, Self::Error> {
                match n.inner {
                    Inner::I32(i) => Self::try_from(i).map_err(|e| Error::from(__!("{}", e))),
                    Inner::I64(i) => Self::try_from(i).map_err(|e| Error::from(__!("{}", e))),
                    Inner::I128(i) => Self::try_from(i).map_err(|e| Error::from(__!("{}", e))),
                    Inner::ISize(i) => Self::try_from(i).map_err(|e| Error::from(__!("{}", e))),

                    Inner::U32(u) => Self::try_from(u).map_err(|e| Error::from(__!("{}", e))),
                    Inner::U64(u) => Self::try_from(u).map_err(|e| Error::from(__!("{}", e))),
                    Inner::U128(u) => Self::try_from(u).map_err(|e| Error::from(__!("{}", e))),
                    Inner::USize(u) => Self::try_from(u).map_err(|e| Error::from(__!("{}", e))),

                    _ => Err(Error::from(__!("Floating numbers are not supported"))),
                }
            }

        }

        impl TryFrom<Number> for $ty {

            type Error = Error;

            fn try_from(n: Number) -> Result<Self, Self::Error> {
                Self::try_from(&n)
            }

        }
    )+
}}

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

impl TryFrom<&Number> for f32 {

    type Error = Error;

    fn try_from(n: &Number) -> Result<Self, Self::Error> {
        match n.inner {
            Inner::F32(f) => Ok(f),
            _ => Err(Error::from(__!("Not supported"))),
        }
    }

}

impl TryFrom<Number> for f32 {

    type Error = Error;

    fn try_from(n: Number) -> Result<Self, Self::Error> {
        Self::try_from(&n)
    }

}

impl TryFrom<&Number> for f64 {

    type Error = Error;

    fn try_from(n: &Number) -> Result<Self, Self::Error> {
        match n.inner {
            Inner::I32(i) => Ok(Self::from(i)),
            Inner::U32(u) => Ok(Self::from(u)),

            Inner::F32(f) => Ok(Self::from(f)),
            Inner::F64(f) => Ok(f),

            _ => Err(Error::from(__!("Not supported"))),
        }
    }

}

impl TryFrom<Number> for f64 {

    type Error = Error;

    fn try_from(n: Number) -> Result<Self, Self::Error> {
        Self::try_from(&n)
    }

}
