use num::bigint::BigInt;
use num::traits::cast::ToPrimitive;
use num::traits::sign::Signed;
use std::borrow::Cow;
use std::cmp::{Ord, Ordering, PartialOrd};
use std::convert::TryFrom;
use std::convert::TryInto;
use std::fmt;

// Invariant: if I128 can be used, it will be; otherwise, if U128 can
// be used, it will be; otherwise, Big will be used.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SignedIntegerRepr {
    I128(i128),
    U128(u128),
    Big(Box<BigInt>),
}

#[derive(Clone, PartialEq, Eq, Hash)]
pub struct SignedInteger(SignedIntegerRepr);

impl fmt::Debug for SignedInteger {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}n", self)
    }
}

impl fmt::Display for SignedInteger {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        match self.repr() {
            SignedIntegerRepr::I128(i) => i.fmt(f),
            SignedIntegerRepr::U128(u) => u.fmt(f),
            SignedIntegerRepr::Big(n) => n.fmt(f),
        }
    }
}

impl Ord for SignedInteger {
    fn cmp(&self, other: &Self) -> Ordering {
        match self.repr() {
            SignedIntegerRepr::I128(i1) => match other.repr() {
                SignedIntegerRepr::I128(i2) => i1.cmp(i2),
                SignedIntegerRepr::U128(_) => if *i1 < 0 { Ordering::Less } else { Ordering::Greater },
                SignedIntegerRepr::Big(n) => if n.is_negative() { Ordering::Less } else { Ordering::Greater },
            },
            SignedIntegerRepr::U128(u1) => match other.repr() {
                SignedIntegerRepr::I128(_) => Ordering::Greater,
                SignedIntegerRepr::U128(u2) => u1.cmp(u2),
                SignedIntegerRepr::Big(n) => if n.is_positive() { Ordering::Less } else { Ordering::Greater },
            },
            SignedIntegerRepr::Big(n1) => match other.repr() {
                SignedIntegerRepr::I128(_) |
                SignedIntegerRepr::U128(_) => if n1.is_negative() { Ordering::Less } else { Ordering::Greater },
                SignedIntegerRepr::Big(n2) => n1.cmp(n2),
            },
        }
    }
}

impl PartialOrd for SignedInteger {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl SignedInteger {
    pub fn repr(&self) -> &SignedIntegerRepr {
        &self.0
    }

    pub fn is_i(&self) -> bool {
        matches!(self.0, SignedIntegerRepr::I128(_))
    }

    pub fn is_u(&self) -> bool {
        matches!(self.0, SignedIntegerRepr::U128(_))
    }

    pub fn is_big(&self) -> bool {
        matches!(self.0, SignedIntegerRepr::Big(_))
    }
}

//--

macro_rules! map_integral_type_to_signed_integer {
    ($iN:ident, $uN:ident) => {
        impl From<$iN> for SignedInteger {
            fn from(v: $iN) -> Self {
                SignedInteger(SignedIntegerRepr::I128(v.into()))
            }
        }

        impl From<$uN> for SignedInteger {
            fn from(v: $uN) -> Self {
                SignedInteger(SignedIntegerRepr::I128(v.into()))
            }
        }

        impl TryFrom<&SignedInteger> for $iN {
            type Error = ();
            fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
                i128::try_from(v)?.try_into().map_err(|_| ())
            }
        }

        impl TryFrom<&SignedInteger> for $uN {
            type Error = ();
            fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
                u128::try_from(v)?.try_into().map_err(|_| ())
            }
        }
    }
}

map_integral_type_to_signed_integer!(i8, u8);
map_integral_type_to_signed_integer!(i16, u16);
map_integral_type_to_signed_integer!(i32, u32);
map_integral_type_to_signed_integer!(i64, u64);

//--

impl From<i128> for SignedInteger {
    fn from(v: i128) -> Self {
        SignedInteger(SignedIntegerRepr::I128(v))
    }
}

impl From<u128> for SignedInteger {
    fn from(v: u128) -> Self {
        if let Ok(w) = v.try_into() {
            SignedInteger(SignedIntegerRepr::I128(w))
        } else {
            SignedInteger(SignedIntegerRepr::U128(v))
        }
    }
}

impl TryFrom<&SignedInteger> for i128 {
    type Error = ();
    fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
        match v.repr() {
            SignedIntegerRepr::I128(i) => Ok(*i),
            SignedIntegerRepr::U128(_) => Err(()),
            SignedIntegerRepr::Big(_) => Err(()),
        }
    }
}

impl TryFrom<&SignedInteger> for u128 {
    type Error = ();
    fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
        match v.repr() {
            SignedIntegerRepr::I128(i) => i.to_u128().ok_or(()),
            SignedIntegerRepr::U128(u) => Ok(*u),
            SignedIntegerRepr::Big(_) => Err(()),
        }
    }
}

//--

impl From<BigInt> for SignedInteger {
    fn from(v: BigInt) -> Self {
        Self::from(Cow::Owned(v))
    }
}

impl<'a> From<&'a SignedInteger> for BigInt {
    fn from(v: &'a SignedInteger) -> Self {
        match v.repr() {
            SignedIntegerRepr::I128(i) => BigInt::from(*i),
            SignedIntegerRepr::U128(u) => BigInt::from(*u),
            SignedIntegerRepr::Big(n) => *n.clone(),
        }
    }
}

//--

impl<'a> From<Cow<'a, BigInt>> for SignedInteger {
    fn from(v: Cow<'a, BigInt>) -> Self {
        if let Some(w) = v.to_i128() {
            SignedInteger(SignedIntegerRepr::I128(w))
        } else if let Some(w) = v.to_u128() {
            SignedInteger(SignedIntegerRepr::U128(w))
        } else {
            SignedInteger(SignedIntegerRepr::Big(Box::new(v.into_owned())))
        }
    }
}

impl<'a> From<&'a SignedInteger> for Cow<'a, BigInt> {
    fn from(v: &'a SignedInteger) -> Self {
        match v.repr() {
            SignedIntegerRepr::I128(i) => Cow::Owned(BigInt::from(*i)),
            SignedIntegerRepr::U128(u) => Cow::Owned(BigInt::from(*u)),
            SignedIntegerRepr::Big(n) => Cow::Borrowed(n),
        }
    }
}

//--

impl From<isize> for SignedInteger {
    fn from(v: isize) -> Self {
        SignedInteger(SignedIntegerRepr::I128(v as i128))
    }
}

impl From<usize> for SignedInteger {
    fn from(v: usize) -> Self {
        SignedInteger(SignedIntegerRepr::U128(v as u128))
    }
}

impl TryFrom<&SignedInteger> for isize {
    type Error = ();
    fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
        i128::try_from(v)?.try_into().map_err(|_| ())
    }
}

impl TryFrom<&SignedInteger> for usize {
    type Error = ();
    fn try_from(v: &SignedInteger) -> Result<Self, Self::Error> {
        u128::try_from(v)?.try_into().map_err(|_| ())
    }
}
