use std::{
    cmp::Ordering,
    fmt,
    hash::{Hash, Hasher},
    num::TryFromIntError,
    ops::{Add, Div, DivAssign, Mul, Neg, Rem, RemAssign, Sub, SubAssign},
};

pub struct NumberConvertError;

impl From<TryFromIntError> for NumberConvertError {
    fn from(_: TryFromIntError) -> Self {
        Self
    }
}

#[derive(Debug, Clone, Copy)]
pub enum NumberValue {
    Int(i64),
    Float(f64),
}

impl NumberValue {
    pub(super) fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
        match self {
            Self::Int(value) => {
                state.write_u8(1);
                value.hash(state)
            }
            Self::Float(value) if !value.is_nan() => {
                state.write_u8(2);
                state.write(&value.to_le_bytes());
            }
            _ => return false,
        }
        true
    }

    pub const fn nan() -> Self {
        Self::Float(f64::NAN)
    }

    pub const fn infinity() -> Self {
        Self::Float(f64::INFINITY)
    }

    pub const fn neg_infinity() -> Self {
        Self::Float(f64::NEG_INFINITY)
    }

    pub fn reciprocal(self) -> Self {
        Self::Float(1.0 / self.as_float())
    }

    pub fn from_str_radix(string: &str, radix: u32) -> Option<Self> {
        if !(2..=36).contains(&radix) {
            return None;
        }
        if let Ok(value) = i64::from_str_radix(string, radix) {
            Some(Self::Int(value))
        } else if radix == 10
            && let Ok(value) = string.parse()
        {
            Some(Self::Float(value))
        } else {
            None
        }
    }

    pub fn as_float(self) -> f64 {
        match self {
            Self::Int(value) => value as f64,
            Self::Float(value) => value,
        }
    }

    pub fn is_trueish(&self) -> bool {
        match self {
            Self::Int(value) => *value != 0,
            Self::Float(value) => *value != 0.0 && !value.is_nan(),
        }
    }
}

impl PartialEq for NumberValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Int(a), Self::Int(b)) => a == b,
            _ => self.as_float() == other.as_float(),
        }
    }
}

impl PartialOrd for NumberValue {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        match (self, other) {
            (Self::Int(a), Self::Int(b)) => PartialOrd::partial_cmp(a, b),
            _ => PartialOrd::partial_cmp(&self.as_float(), &other.as_float()),
        }
    }
}

impl Add for NumberValue {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        match (self, rhs) {
            (Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_add(b) => Self::Int(c),
            _ => todo!("TODO promote to bigint"),
        }
    }
}

impl Sub for NumberValue {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        match (self, rhs) {
            (Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_sub(b) => Self::Int(c),
            _ => todo!("TODO promote to bigint"),
        }
    }
}

impl SubAssign for NumberValue {
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}

impl Mul for NumberValue {
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        match (self, rhs) {
            (Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_mul(b) => Self::Int(c),
            _ => todo!("TODO promote to bigint"),
        }
    }
}

impl Div for NumberValue {
    type Output = Self;

    fn div(self, rhs: Self) -> Self::Output {
        match (self, rhs) {
            // TODO convert inexact division to rational
            (Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_div_exact(b) => Self::Int(c),
            _ => Self::Float(self.as_float() / rhs.as_float()),
        }
    }
}

impl DivAssign for NumberValue {
    fn div_assign(&mut self, rhs: Self) {
        *self = *self / rhs;
    }
}

impl Rem for NumberValue {
    type Output = Self;

    fn rem(self, rhs: Self) -> Self::Output {
        match (self, rhs) {
            (Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_rem(b) => Self::Int(c),
            _ => todo!("TODO NaN"),
        }
    }
}

impl RemAssign for NumberValue {
    fn rem_assign(&mut self, rhs: Self) {
        *self = *self % rhs;
    }
}

impl Neg for NumberValue {
    type Output = NumberValue;

    fn neg(self) -> Self::Output {
        match self {
            Self::Int(a) if let Some(b) = a.checked_neg() => Self::Int(b),
            _ => todo!("TODO promote to bigint"),
        }
    }
}

impl From<i8> for NumberValue {
    fn from(value: i8) -> Self {
        Self::Int(value.into())
    }
}
impl From<i16> for NumberValue {
    fn from(value: i16) -> Self {
        Self::Int(value.into())
    }
}
impl From<i32> for NumberValue {
    fn from(value: i32) -> Self {
        Self::Int(value.into())
    }
}
impl From<i64> for NumberValue {
    fn from(value: i64) -> Self {
        Self::Int(value)
    }
}
impl From<usize> for NumberValue {
    fn from(value: usize) -> Self {
        if let Ok(value) = i64::try_from(value) {
            Self::Int(value)
        } else {
            todo!("TODO: promote to bigint")
        }
    }
}

impl TryFrom<NumberValue> for i8 {
    type Error = NumberConvertError;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        let NumberValue::Int(value) = value else {
            return Err(NumberConvertError);
        };
        value.try_into().map_err(From::from)
    }
}
impl TryFrom<NumberValue> for i16 {
    type Error = NumberConvertError;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        let NumberValue::Int(value) = value else {
            return Err(NumberConvertError);
        };
        value.try_into().map_err(From::from)
    }
}
impl TryFrom<NumberValue> for i32 {
    type Error = NumberConvertError;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        let NumberValue::Int(value) = value else {
            return Err(NumberConvertError);
        };
        value.try_into().map_err(From::from)
    }
}
impl TryFrom<NumberValue> for i64 {
    type Error = NumberConvertError;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        let NumberValue::Int(value) = value else {
            return Err(NumberConvertError);
        };
        Ok(value)
    }
}

impl TryFrom<NumberValue> for usize {
    type Error = NumberConvertError;

    fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
        match value {
            NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(value),
            _ => Err(NumberConvertError),
        }
    }
}

impl fmt::Display for NumberValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Int(value) => fmt::Display::fmt(value, f),
            Self::Float(value) if value.is_infinite() && value.is_sign_negative() => {
                write!(f, "-#inf")
            }
            Self::Float(value) if value.is_infinite() => write!(f, "#inf"),
            Self::Float(value) if value.is_nan() => write!(f, "#nan"),
            Self::Float(value) => fmt::Display::fmt(value, f),
        }
    }
}
