use core::cmp::Ordering;

use dashu_base::{
    Approximation::{self, *},
    BitTest, ConversionError, DivRem, FloatEncoding, PowerOfTwo, Sign, UnsignedAbs,
};
use dashu_int::{IBig, UBig};

use crate::{
    rbig::{RBig, Relaxed},
    repr::Repr,
};

impl From<UBig> for Repr {
    #[inline]
    fn from(v: UBig) -> Self {
        Repr {
            numerator: v.into(),
            denominator: UBig::ONE,
        }
    }
}

impl From<IBig> for Repr {
    #[inline]
    fn from(v: IBig) -> Self {
        Repr {
            numerator: v,
            denominator: UBig::ONE,
        }
    }
}

impl TryFrom<Repr> for UBig {
    type Error = ConversionError;
    #[inline]
    fn try_from(value: Repr) -> Result<Self, Self::Error> {
        let (sign, mag) = value.numerator.into_parts();
        if sign == Sign::Negative {
            Err(ConversionError::OutOfBounds)
        } else if mag.is_one() {
            Ok(mag)
        } else {
            Err(ConversionError::LossOfPrecision)
        }
    }
}

impl TryFrom<Repr> for IBig {
    type Error = ConversionError;
    #[inline]
    fn try_from(value: Repr) -> Result<Self, Self::Error> {
        if value.denominator.is_one() {
            Ok(value.numerator)
        } else {
            Err(ConversionError::LossOfPrecision)
        }
    }
}

macro_rules! forward_conversion_to_repr {
    ($from:ty => $t:ident) => {
        impl From<$from> for $t {
            #[inline]
            fn from(v: $from) -> Self {
                $t(Repr::from(v))
            }
        }
        impl TryFrom<$t> for $from {
            type Error = ConversionError;
            #[inline]
            fn try_from(value: $t) -> Result<Self, Self::Error> {
                Self::try_from(value.0)
            }
        }
    };
}
forward_conversion_to_repr!(UBig => RBig);
forward_conversion_to_repr!(IBig => RBig);
forward_conversion_to_repr!(UBig => Relaxed);
forward_conversion_to_repr!(IBig => Relaxed);

macro_rules! impl_conversion_for_prim_ints {
    ($($t:ty)*) => {$(
        impl From<$t> for Repr {
            #[inline]
            fn from(v: $t) -> Repr {
                Repr {
                    numerator: v.into(),
                    denominator: UBig::ONE
                }
            }
        }

        impl TryFrom<Repr> for $t {
            type Error = ConversionError;
            #[inline]
            fn try_from(value: Repr) -> Result<Self, Self::Error> {
                let int: IBig = value.try_into()?;
                int.try_into()
            }
        }

        forward_conversion_to_repr!($t => RBig);
        forward_conversion_to_repr!($t => Relaxed);
    )*};
}
impl_conversion_for_prim_ints!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize);

macro_rules! impl_conversion_from_float {
    ($t:ty) => {
        impl TryFrom<$t> for Repr {
            type Error = ConversionError;

            fn try_from(value: $t) -> Result<Self, Self::Error> {
                // shortcut to prevent issues in counting leading zeros
                if value == 0. {
                    return Ok(Repr::zero());
                }

                match value.decode() {
                    Ok((man, exp)) => {
                        // here we don't remove the common factor 2, because we need exact
                        // exponent value in some cases (like approx_f32 and approx_f64)
                        let repr = if exp >= 0 {
                            Repr {
                                numerator: IBig::from(man) << exp as usize,
                                denominator: UBig::ONE,
                            }
                        } else {
                            let mut denominator = UBig::ZERO;
                            denominator.set_bit((-exp) as _);
                            Repr {
                                numerator: IBig::from(man),
                                denominator,
                            }
                        };
                        Ok(repr)
                    }
                    Err(_) => Err(ConversionError::OutOfBounds),
                }
            }
        }

        impl TryFrom<$t> for RBig {
            type Error = ConversionError;
            #[inline]
            fn try_from(value: $t) -> Result<Self, Self::Error> {
                Repr::try_from(value).map(|repr| RBig(repr.reduce2()))
            }
        }
        impl TryFrom<$t> for Relaxed {
            type Error = ConversionError;
            #[inline]
            fn try_from(value: $t) -> Result<Self, Self::Error> {
                Repr::try_from(value).map(|repr| Relaxed(repr.reduce2()))
            }
        }
    };
}
impl_conversion_from_float!(f32);
impl_conversion_from_float!(f64);

macro_rules! impl_conversion_to_float {
    ($t:ty [$lb:literal, $ub:literal]) => {
        impl TryFrom<RBig> for $t {
            type Error = ConversionError;

            /// Convert RBig to primitive floats. It returns [Ok] only if
            /// the conversion can be done losslessly
            fn try_from(value: RBig) -> Result<Self, Self::Error> {
                if value.0.numerator.is_zero() {
                    Ok(0.)
                } else if value.0.denominator.is_power_of_two() {
                    // conversion is exact only if the denominator is a power of two
                    let num_bits = value.0.numerator.bit_len();
                    let den_bits = value.0.denominator.trailing_zeros().unwrap();
                    let top_bit = num_bits as isize - den_bits as isize;
                    if top_bit > $ub {
                        // see to_f32::encode for explanation of the bounds
                        Err(ConversionError::OutOfBounds)
                    } else if top_bit < $lb {
                        Err(ConversionError::LossOfPrecision)
                    } else {
                        match <$t>::encode(
                            value.0.numerator.try_into().unwrap(),
                            -(den_bits as i16),
                        ) {
                            Exact(v) => Ok(v),
                            Inexact(v, _) => {
                                if v.is_infinite() {
                                    Err(ConversionError::OutOfBounds)
                                } else {
                                    Err(ConversionError::LossOfPrecision)
                                }
                            }
                        }
                    }
                } else {
                    Err(ConversionError::LossOfPrecision)
                }
            }
        }

        impl TryFrom<Relaxed> for $t {
            type Error = ConversionError;

            #[inline]
            fn try_from(value: Relaxed) -> Result<Self, Self::Error> {
                // convert to RBig to eliminate cofactors
                <$t>::try_from(value.canonicalize())
            }
        }
    };
}
impl_conversion_to_float!(f32 [-149, 128]); // see f32::encode for explanation of the bounds
impl_conversion_to_float!(f64 [-1074, 1024]); // see f32::encode for explanation of the bounds

impl Repr {
    /// Convert the rational number to [f32] without guaranteed correct rounding.
    fn to_f32_fast(&self) -> f32 {
        // shortcut
        if self.numerator.is_zero() {
            return 0.;
        }

        // to get enough precision (24 bits), we need to do a 48 by 24 bit division
        let sign = self.numerator.sign();
        let num_bits = self.numerator.bit_len();
        let den_bits = self.denominator.bit_len();

        let num_shift = num_bits as isize - 48;
        let num48: i64 = if num_shift >= 0 {
            (&self.numerator) >> num_shift as usize
        } else {
            (&self.numerator) << (-num_shift) as usize
        }
        .try_into()
        .unwrap();

        let den_shift = den_bits as isize - 24;
        let den24: u32 = if den_shift >= 0 {
            (&self.denominator) >> den_shift as usize
        } else {
            (&self.denominator) << (-den_shift) as usize
        }
        .try_into()
        .unwrap();

        // determine the exponent
        let exponent = num_shift - den_shift;
        if exponent >= 128 {
            // max f32 = 2^128 * (1 - 2^-24)
            sign * f32::INFINITY
        } else if exponent < -149 - 25 {
            // min f32 = 2^-149, quotient has at most 25 bits
            sign * 0f32
        } else {
            let (mut man, r) = num48.unsigned_abs().div_rem(den24 as u64);

            // round to nearest, ties to even
            let half = (r as u32 * 2).cmp(&den24);
            if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
                man += 1;
            }
            f32::encode(sign * man as i32, exponent as i16).value()
        }
    }

    fn to_f64_fast(&self) -> f64 {
        // shortcut
        if self.numerator.is_zero() {
            return 0.;
        }

        // to get enough precision (53 bits), we need to do a 106 by 53 bit division
        let sign = self.numerator.sign();
        let num_bits = self.numerator.bit_len();
        let den_bits = self.denominator.bit_len();

        let num_shift = num_bits as isize - 106;
        let num106: i128 = if num_shift >= 0 {
            (&self.numerator) >> num_shift as usize
        } else {
            (&self.numerator) << (-num_shift) as usize
        }
        .try_into()
        .unwrap();

        let den_shift = den_bits as isize - 53;
        let den53: u64 = if den_shift >= 0 {
            (&self.denominator) >> den_shift as usize
        } else {
            (&self.denominator) << (-den_shift) as usize
        }
        .try_into()
        .unwrap();

        // determine the exponent
        let exponent = num_shift - den_shift;
        if exponent >= 1024 {
            // max f64 = 2^1024 × (1 − 2^−53)
            sign * f64::INFINITY
        } else if exponent < -1074 - 54 {
            // min f64 = 2^-1074, quotient has at most 54 bits
            sign * 0f64
        } else {
            let (mut man, r) = num106.unsigned_abs().div_rem(den53 as u128);

            // round to nearest, ties to even
            let half = (r as u64 * 2).cmp(&den53);
            if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
                man += 1;
            }
            f64::encode(sign * man as i64, exponent as i16).value()
        }
    }

    /// Convert the rational number to [f32] with guaranteed correct rounding.
    fn to_f32(&self) -> Approximation<f32, Sign> {
        // shortcut
        if self.numerator.is_zero() {
            return Exact(0.);
        }

        // to get enough precision, shift such that numerator has
        // 24 bits more than the denominator
        let sign = self.numerator.sign();
        let num_bits = self.numerator.bit_len();
        let den_bits = self.denominator.bit_len();

        let shift = num_bits as isize - den_bits as isize - 24; // i.e. exponent
        let (num, den) = if shift >= 0 {
            (self.numerator.clone(), (&self.denominator) << shift as usize)
        } else {
            ((&self.numerator) << (-shift) as usize, self.denominator.clone())
        };

        // then construct the
        if shift >= 128 {
            // max f32 = 2^128 * (1 - 2^-24)
            Inexact(sign * f32::INFINITY, sign)
        } else if shift < -149 - 25 {
            // min f32 = 2^-149, quotient has at most 25 bits
            Inexact(sign * 0f32, -sign)
        } else {
            let (man, r) = num.unsigned_abs().div_rem(&den);
            let man: u32 = man.try_into().unwrap();

            // round to nearest, ties to even
            if r.is_zero() {
                Exact(man)
            } else {
                let half = (r << 1).cmp(&den);
                if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
                    Inexact(man + 1, sign)
                } else {
                    Inexact(man, -sign)
                }
            }
            .and_then(|man| f32::encode(sign * man as i32, shift as i16))
        }
    }

    fn to_f64(&self) -> Approximation<f64, Sign> {
        // shortcut
        if self.numerator.is_zero() {
            return Exact(0.);
        }

        // to get enough precision, shift such that numerator has
        // 53 bits more than the denominator
        let sign = self.numerator.sign();
        let num_bits = self.numerator.bit_len();
        let den_bits = self.denominator.bit_len();

        let shift = num_bits as isize - den_bits as isize - 53; // i.e. exponent
        let (num, den) = if shift >= 0 {
            (self.numerator.clone(), (&self.denominator) << shift as usize)
        } else {
            ((&self.numerator) << (-shift) as usize, self.denominator.clone())
        };

        // then construct the
        if shift >= 1024 {
            // max f64 = 2^1024 × (1 − 2^−53)
            Inexact(sign * f64::INFINITY, sign)
        } else if shift < -1074 - 53 {
            // min f64 = 2^-1074, quotient has at most 53 bits
            Inexact(sign * 0f64, -sign)
        } else {
            let (man, r) = num.unsigned_abs().div_rem(&den);
            let man: u64 = man.try_into().unwrap();

            // round to nearest, ties to even
            if r.is_zero() {
                Exact(man)
            } else {
                let half = (r << 1).cmp(&den);
                if half == Ordering::Greater || (half == Ordering::Equal && man & 1 > 0) {
                    Inexact(man + 1, sign)
                } else {
                    Inexact(man, -sign)
                }
            }
            .and_then(|man| f64::encode(sign * man as i64, shift as i16))
        }
    }
}

impl RBig {
    /// Convert the rational number to a [f32].
    ///
    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
    /// ties to even).
    ///
    /// The rounding will be correct at most of the time, but in rare cases the
    /// mantissa can be off by one bit. Use [RBig::to_f32] for ensured correct
    /// rounding.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_ratio::RBig;
    /// assert_eq!(RBig::ONE.to_f32_fast(), 1f32);
    ///
    /// let r = RBig::from_parts(22.into(), 7u8.into());
    /// assert_eq!(r.to_f32_fast(), 22./7.)
    /// ```
    #[inline]
    pub fn to_f32_fast(&self) -> f32 {
        self.0.to_f32_fast()
    }

    /// Convert the rational number to a [f64].
    ///
    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
    /// ties to even).
    ///
    /// The rounding will be correct at most of the time, but in rare cases the
    /// mantissa can be off by one bit. Use [RBig::to_f64] for ensured correct
    /// rounding.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_ratio::RBig;
    /// assert_eq!(RBig::ONE.to_f64_fast(), 1f64);
    ///
    /// let r = RBig::from_parts(22.into(), 7u8.into());
    /// assert_eq!(r.to_f64_fast(), 22./7.)
    /// ```
    #[inline]
    pub fn to_f64_fast(&self) -> f64 {
        self.0.to_f64_fast()
    }

    /// Convert the rational number to a [f32] with guaranteed correct rounding.
    ///
    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
    /// ties to even).
    ///
    /// Because of the guaranteed rounding, it might take a long time to convert
    /// when the numerator and denominator are large. In this case [RBig::to_f32_fast]
    /// can be used if the correct rounding is not required.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_base::{Approximation::*, Sign::*};
    /// # use dashu_ratio::RBig;
    /// assert_eq!(RBig::ONE.to_f32(), Exact(1f32));
    ///
    /// let r = RBig::from_parts(22.into(), 7u8.into());
    /// // f32 representation of 22/7 is smaller than the actual 22/7
    /// assert_eq!(r.to_f32(), Inexact(22./7., Negative));
    /// ```
    #[inline]
    pub fn to_f32(&self) -> Approximation<f32, Sign> {
        self.0.to_f32()
    }

    /// Convert the rational number to a [f64] with guaranteed correct rounding.
    ///
    /// The rounding follows the default IEEE 754 behavior (rounds to nearest,
    /// ties to even).
    ///
    /// Because of the guaranteed rounding, it might take a long time to convert
    /// when the numerator and denominator are large. In this case [RBig::to_f64_fast]
    /// can be used if the correct rounding is not required.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_base::{Approximation::*, Sign::*};
    /// # use dashu_ratio::RBig;
    /// assert_eq!(RBig::ONE.to_f64(), Exact(1f64));
    ///
    /// let r = RBig::from_parts(22.into(), 7u8.into());
    /// // f64 representation of 22/7 is smaller than the actual 22/7
    /// assert_eq!(r.to_f64(), Inexact(22./7., Negative));
    /// ```
    #[inline]
    pub fn to_f64(&self) -> Approximation<f64, Sign> {
        self.0.to_f64()
    }

    /// Convert the rational number to an [IBig].
    ///
    /// The conversion rounds toward zero. It's equivalent to [RBig::trunc],
    /// but it returns the fractional part if the rational number is not an integer.
    ///
    /// # Examples
    ///
    /// ```
    /// # use dashu_base::Approximation::*;
    /// # use dashu_int::{IBig, UBig};
    /// # use dashu_ratio::RBig;
    /// let a = RBig::from_parts(22.into(), UBig::ONE);
    /// assert_eq!(a.to_int(), Exact(IBig::from(22)));
    ///
    /// let b = RBig::from_parts(22.into(), 7u8.into());
    /// assert_eq!(b.to_int(), Inexact(
    ///     IBig::from(3), RBig::from_parts(1.into(), 7u8.into())
    /// ));
    /// ```
    #[inline]
    pub fn to_int(&self) -> Approximation<IBig, Self> {
        let (trunc, fract) = self.clone().split_at_point();
        if fract.is_zero() {
            Approximation::Exact(trunc)
        } else {
            Approximation::Inexact(trunc, fract)
        }
    }
}

impl Relaxed {
    /// Convert the rational number to a [f32].
    ///
    /// See [RBig::to_f32_fast] for details.
    #[inline]
    pub fn to_f32_fast(&self) -> f32 {
        self.0.to_f32_fast()
    }
    /// Convert the rational number to a [f64].
    ///
    /// See [RBig::to_f64_fast] for details.
    #[inline]
    pub fn to_f64_fast(&self) -> f64 {
        self.0.to_f64_fast()
    }

    /// Convert the rational number to a [f32] with guaranteed correct rounding.
    ///
    /// See [RBig::to_f32] for details.
    #[inline]
    pub fn to_f32(&self) -> Approximation<f32, Sign> {
        self.0.to_f32()
    }
    /// Convert the rational number to a [f64] with guaranteed correct rounding.
    ///
    /// See [RBig::to_f64] for details.
    #[inline]
    pub fn to_f64(&self) -> Approximation<f64, Sign> {
        self.0.to_f64()
    }
    /// Convert the rational number to am [IBig].
    ///
    /// See [RBig::to_int] for details.
    #[inline]
    pub fn to_int(&self) -> Approximation<IBig, Self> {
        let (trunc, fract) = self.clone().split_at_point();
        if fract.is_zero() {
            Approximation::Exact(trunc)
        } else {
            Approximation::Inexact(trunc, fract)
        }
    }
}
