use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[cfg(feature = "serde")]
use serde::de::Unexpected;
enum CoerceResult {
    PosInt(u64, u64),
    NegInt(i64, i64),
    Float(f64, f64),
}
fn coerce(a: N, b: N) -> CoerceResult {
    match (a, b) {
        (N::PosInt(a), N::PosInt(b)) => CoerceResult::PosInt(a, b),
        (N::NegInt(a), N::NegInt(b)) => CoerceResult::NegInt(a, b),
        (N::Float(a), N::Float(b)) => CoerceResult::Float(a, b),
        (a, b) => CoerceResult::Float(a.to_f64(), b.to_f64()),
    }
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd)]
pub struct Number {
    n: N,
}
#[derive(Clone, Copy)]
enum N {
    PosInt(u64),
    NegInt(i64),
    Float(f64),
}
impl N {
    fn from_finite_f64(value: f64) -> N {
        debug_assert!(value.is_finite());
        #[cfg(feature = "std")]
        let no_fraction = value.fract() == 0.0;
        #[cfg(not(feature = "std"))]
        #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
        let no_fraction = value - (value as i64 as f64) == 0.0;
        if no_fraction {
            #[allow(clippy::cast_possible_truncation)]
            N::from(value as i64)
        } else {
            N::Float(value)
        }
    }
    fn as_i64(&self) -> Option<i64> {
        match *self {
            N::PosInt(n) => i64::try_from(n).ok(),
            N::NegInt(n) => Some(n),
            N::Float(_) => None,
        }
    }
    fn as_u64(&self) -> Option<u64> {
        match *self {
            N::PosInt(n) => Some(n),
            N::NegInt(n) => u64::try_from(n).ok(),
            N::Float(_) => None,
        }
    }
    fn to_f64(self) -> f64 {
        #[allow(clippy::cast_precision_loss)]
        match self {
            N::PosInt(n) => n as f64,
            N::NegInt(n) => n as f64,
            N::Float(n) => n,
        }
    }
    fn is_f64(&self) -> bool {
        match self {
            N::Float(_) => true,
            N::PosInt(_) | N::NegInt(_) => false,
        }
    }
    fn is_i64(&self) -> bool {
        match self {
            N::NegInt(_) => true,
            N::PosInt(_) | N::Float(_) => false,
        }
    }
    fn is_u64(&self) -> bool {
        match self {
            N::PosInt(_) => true,
            N::NegInt(_) | N::Float(_) => false,
        }
    }
}
impl PartialEq for N {
    fn eq(&self, other: &Self) -> bool {
        match coerce(*self, *other) {
            CoerceResult::PosInt(a, b) => a == b,
            CoerceResult::NegInt(a, b) => a == b,
            CoerceResult::Float(a, b) => a == b,
        }
    }
}
impl Eq for N {}
impl PartialOrd for N {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match coerce(*self, *other) {
            CoerceResult::PosInt(a, b) => a.partial_cmp(&b),
            CoerceResult::NegInt(a, b) => a.partial_cmp(&b),
            CoerceResult::Float(a, b) => a.partial_cmp(&b),
        }
    }
}
impl Hash for N {
    fn hash<H>(&self, h: &mut H)
    where
        H: Hasher,
    {
        match *self {
            N::PosInt(n) => n.hash(h),
            N::NegInt(n) => n.hash(h),
            N::Float(n) => {
                if n == 0.0f64 {
                    0.0f64.to_bits().hash(h);
                } else {
                    n.to_bits().hash(h);
                }
            }
        }
    }
}
impl From<i64> for N {
    fn from(i: i64) -> Self {
        if i < 0 {
            N::NegInt(i)
        } else {
            #[allow(clippy::cast_sign_loss)]
            N::PosInt(i as u64)
        }
    }
}
impl Neg for N {
    type Output = N;
    fn neg(self) -> Self::Output {
        match self {
            #[allow(clippy::cast_possible_wrap)]
            N::PosInt(value) => N::NegInt(-(value as i64)),
            N::NegInt(value) => N::from(-value),
            N::Float(value) => N::Float(-value),
        }
    }
}
impl Add for N {
    type Output = N;
    fn add(self, rhs: Self) -> Self::Output {
        match coerce(self, rhs) {
            CoerceResult::PosInt(a, b) => N::PosInt(a + b),
            CoerceResult::NegInt(a, b) => N::NegInt(a + b),
            CoerceResult::Float(a, b) => N::from_finite_f64(a + b),
        }
    }
}
impl Sub for N {
    type Output = N;
    fn sub(self, rhs: Self) -> Self::Output {
        match coerce(self, rhs) {
            CoerceResult::PosInt(a, b) => {
                if b > a {
                    #[allow(clippy::cast_possible_wrap)]
                    N::NegInt(a as i64 - b as i64)
                } else {
                    N::PosInt(a - b)
                }
            }
            CoerceResult::NegInt(a, b) => N::from(a - b),
            CoerceResult::Float(a, b) => N::from_finite_f64(a - b),
        }
    }
}
impl Mul for N {
    type Output = N;
    fn mul(self, rhs: Self) -> Self::Output {
        match coerce(self, rhs) {
            CoerceResult::PosInt(a, b) => N::PosInt(a * b),
            CoerceResult::NegInt(a, b) => N::from(a * b),
            CoerceResult::Float(a, b) => N::from_finite_f64(a * b),
        }
    }
}
impl Div for N {
    type Output = N;
    fn div(self, rhs: Self) -> Self::Output {
        N::from_finite_f64(self.to_f64() / rhs.to_f64())
    }
}
impl Rem for N {
    type Output = N;
    fn rem(self, rhs: Self) -> Self::Output {
        match coerce(self, rhs) {
            CoerceResult::PosInt(a, b) => N::PosInt(a % b),
            CoerceResult::NegInt(a, b) => N::NegInt(a % b),
            CoerceResult::Float(a, b) => N::from_finite_f64(a % b),
        }
    }
}
impl Number {
    pub fn from_f64(f: f64) -> Option<Number> {
        if f.is_finite() {
            Some(Number::from_finite_f64(f))
        } else {
            None
        }
    }
    pub(crate) fn from_finite_f64(f: f64) -> Number {
        Number {
            n: N::from_finite_f64(f),
        }
    }
    #[inline]
    pub fn as_f64(&self) -> Option<f64> {
        Some(self.n.to_f64())
    }
    #[inline]
    pub fn as_i64(&self) -> Option<i64> {
        self.n.as_i64()
    }
    #[inline]
    pub fn as_u64(&self) -> Option<u64> {
        self.n.as_u64()
    }
    #[inline]
    pub fn is_f64(&self) -> bool {
        self.n.is_f64()
    }
    #[inline]
    pub fn is_i64(&self) -> bool {
        self.n.is_i64()
    }
    #[inline]
    pub fn is_u64(&self) -> bool {
        self.n.is_u64()
    }
    #[cfg(feature = "serde")]
    #[doc(hidden)]
    #[cold]
    pub fn unexpected(&self) -> Unexpected {
        match self.n {
            N::PosInt(v) => Unexpected::Unsigned(v),
            N::NegInt(v) => Unexpected::Signed(v),
            N::Float(v) => Unexpected::Float(v),
        }
    }
}
macro_rules! impl_from_unsigned {
    ($($ty:ty),*) => {
        $(
            impl From<$ty> for Number {
                #[inline]
                fn from(u: $ty) -> Self {
                    Number {
                        n: N::PosInt(u as u64)
                    }
                }
            }
        )*
    };
}
macro_rules! impl_from_signed {
    ($($ty:ty),*) => {
        $(
            impl From<$ty> for Number {
                #[inline]
                fn from(i: $ty) -> Self {
                    Number {
                        n: N::from(i as i64)
                    }
                }
            }
        )*
    };
}
macro_rules! impl_binary_ops {
    ($($op:ty => $method:ident),*) => {
        $(
            impl $op for Number {
                type Output = Number;
                fn $method(self, rhs: Self) -> Self::Output {
                    Number {
                        n: self.n.$method(rhs.n)
                    }
                }
            }
        )*
    };
}
impl_from_unsigned!(u8, u16, u32, u64, usize);
impl_from_signed!(i8, i16, i32, i64, isize);
impl_binary_ops!(Add => add, Sub => sub, Mul => mul, Div => div, Rem => rem);
impl Neg for Number {
    type Output = Number;
    fn neg(self) -> Self::Output {
        Number { n: -self.n }
    }
}
impl fmt::Display for Number {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.n {
            N::PosInt(v) => f.write_str(itoa::Buffer::new().format(v)),
            N::NegInt(v) => f.write_str(itoa::Buffer::new().format(v)),
            N::Float(v) => f.write_str(ryu::Buffer::new().format_finite(v)),
        }
    }
}
impl fmt::Debug for Number {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Number({self})")
    }
}
#[cfg(feature = "serde")]
impl serde::Serialize for Number {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        match self.n {
            N::PosInt(v) => serializer.serialize_u64(v),
            N::NegInt(v) => serializer.serialize_i64(v),
            N::Float(v) => serializer.serialize_f64(v),
        }
    }
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Number {
    fn deserialize<D>(deserializer: D) -> Result<Number, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct NumberVisitor;
        impl<'de> serde::de::Visitor<'de> for NumberVisitor {
            type Value = Number;
            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("an HCL number")
            }
            fn visit_i64<E>(self, value: i64) -> Result<Number, E> {
                Ok(value.into())
            }
            fn visit_u64<E>(self, value: u64) -> Result<Number, E> {
                Ok(value.into())
            }
            fn visit_f64<E>(self, value: f64) -> Result<Number, E>
            where
                E: serde::de::Error,
            {
                Number::from_f64(value).ok_or_else(|| serde::de::Error::custom("not an HCL number"))
            }
        }
        deserializer.deserialize_any(NumberVisitor)
    }
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserializer<'de> for Number {
    type Error = super::Error;
    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::Visitor<'de>,
    {
        match self.n {
            N::PosInt(i) => visitor.visit_u64(i),
            N::NegInt(i) => visitor.visit_i64(i),
            N::Float(f) => visitor.visit_f64(f),
        }
    }
    serde::forward_to_deserialize_any! {
        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
        bytes byte_buf option unit unit_struct newtype_struct seq tuple
        tuple_struct enum map struct identifier ignored_any
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    macro_rules! float {
        ($f:expr) => {
            Number::from_finite_f64($f)
        };
    }
    macro_rules! int {
        ($i:expr) => {
            Number::from($i)
        };
    }
    macro_rules! assert_op {
        ($expr:expr, $expected:expr, $check:ident) => {
            let result = $expr;
            assert_eq!(result, $expected, "incorrect number op result");
            assert!(result.$check());
        };
    }
    #[test]
    fn neg() {
        assert_op!(-int!(1u64), int!(-1i64), is_i64);
        assert_op!(-float!(1.5), float!(-1.5), is_f64);
        assert_op!(-float!(1.0), int!(-1i64), is_i64);
    }
    #[test]
    fn add() {
        assert_op!(int!(1i64) + int!(2u64), int!(3), is_u64);
        assert_op!(float!(1.5) + float!(1.5), int!(3), is_u64);
        assert_op!(float!(1.5) + int!(-1i64), float!(0.5), is_f64);
        assert_op!(int!(-1i64) + int!(-2i64), int!(-3i64), is_i64);
    }
    #[test]
    fn sub() {
        assert_op!(int!(1i64) - int!(2u64), int!(-1i64), is_i64);
        assert_op!(int!(-1i64) - int!(-2i64), int!(1u64), is_u64);
        assert_op!(float!(1.5) - float!(1.5), int!(0), is_u64);
        assert_op!(float!(1.5) - int!(-1i64), float!(2.5), is_f64);
    }
    #[test]
    fn mul() {
        assert_op!(int!(-1i64) * int!(2u64), int!(-2i64), is_i64);
        assert_op!(int!(-1i64) * int!(-2i64), int!(2u64), is_u64);
        assert_op!(float!(1.5) * float!(1.5), float!(2.25), is_f64);
        assert_op!(float!(1.5) * int!(-1i64), float!(-1.5), is_f64);
    }
    #[test]
    fn div() {
        assert_op!(int!(1u64) / int!(2u64), float!(0.5), is_f64);
        assert_op!(float!(4.1) / float!(2.0), float!(2.05), is_f64);
        assert_op!(int!(4u64) / int!(2u64), int!(2u64), is_u64);
        assert_op!(int!(-4i64) / int!(2u64), int!(-2i64), is_i64);
        assert_op!(float!(4.0) / float!(2.0), int!(2), is_u64);
        assert_op!(float!(-4.0) / float!(2.0), int!(-2), is_i64);
    }
    #[test]
    fn rem() {
        assert_op!(int!(3u64) % int!(2u64), int!(1u64), is_u64);
        assert_op!(
            float!(4.1) % float!(2.0),
            float!(0.09999999999999964),
            is_f64
        );
        assert_op!(int!(4u64) % int!(2u64), int!(0u64), is_u64);
        assert_op!(int!(-4i64) % int!(3u64), int!(-1i64), is_i64);
        assert_op!(float!(4.0) % float!(2.0), int!(0), is_u64);
        assert_op!(float!(-4.0) % float!(3.0), int!(-1), is_i64);
    }
}