use std::fmt;
use std::ops::Neg;

use traiter::numbers::{CheckedDiv, Gcd, Signed, Zeroable};

use crate::big_int::BigInt;

pub struct Fraction<Component> {
    pub(super) numerator: Component,
    pub(super) denominator: Component,
}

impl<Component: Clone> Clone for Fraction<Component> {
    fn clone(&self) -> Self {
        Self {
            numerator: self.numerator.clone(),
            denominator: self.denominator.clone(),
        }
    }

    fn clone_from(&mut self, source: &Self) {
        (self.numerator, self.denominator) =
            (source.numerator.clone(), source.denominator.clone());
    }
}

impl<
        Component: NormalizeModuli<Output = (Component, Component)>
            + NormalizeSign<Output = (Component, Component)>,
    > Fraction<Component>
where
    for<'a> &'a Component: Zeroable,
{
    pub fn new(
        mut numerator: Component,
        mut denominator: Component,
    ) -> Option<Self> {
        if denominator.is_zero() {
            None
        } else {
            (numerator, denominator) =
                Component::normalize_sign(numerator, denominator);
            (numerator, denominator) =
                Component::normalize_moduli(numerator, denominator);
            Some(Self {
                numerator,
                denominator,
            })
        }
    }
}

impl<Component> Fraction<Component> {
    pub fn denominator(&self) -> &Component {
        &self.denominator
    }

    pub fn numerator(&self) -> &Component {
        &self.numerator
    }
}

pub trait NormalizeModuli<Other = Self> {
    type Output;

    fn normalize_moduli(self, other: Other) -> Self::Output;
}

impl<Digit, const DIGIT_BITNESS: usize> NormalizeModuli
    for BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> Self: CheckedDiv<Output = Option<Self>>
        + CheckedDiv<&'a Self, Output = Option<Self>>,
    for<'a> &'a Self: Gcd<Output = Self>,
{
    type Output = (Self, Self);

    #[inline]
    fn normalize_moduli(self, other: Self) -> Self::Output {
        let gcd = self.gcd(&other);
        (
            unsafe { self.checked_div(&gcd).unwrap_unchecked() },
            unsafe { other.checked_div(gcd).unwrap_unchecked() },
        )
    }
}

impl<Digit, const DIGIT_BITNESS: usize> NormalizeModuli<&Self>
    for BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a Self:
        CheckedDiv<Self, Output = Option<Self>> + Gcd<Output = Self>,
    for<'a> Self: CheckedDiv<&'a Self, Output = Option<Self>>,
{
    type Output = (Self, Self);

    #[inline]
    fn normalize_moduli(self, other: &Self) -> Self::Output {
        let gcd = self.gcd(other);
        (
            unsafe { self.checked_div(&gcd).unwrap_unchecked() },
            unsafe { other.checked_div(gcd).unwrap_unchecked() },
        )
    }
}

impl<Digit, const DIGIT_BITNESS: usize>
    NormalizeModuli<BigInt<Digit, DIGIT_BITNESS>>
    for &BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a BigInt<Digit, DIGIT_BITNESS>: CheckedDiv<Output = Option<BigInt<Digit, DIGIT_BITNESS>>>
        + Gcd<Output = BigInt<Digit, DIGIT_BITNESS>>,
    BigInt<Digit, DIGIT_BITNESS>:
        CheckedDiv<Output = Option<BigInt<Digit, DIGIT_BITNESS>>>,
{
    type Output = (BigInt<Digit, DIGIT_BITNESS>, BigInt<Digit, DIGIT_BITNESS>);

    #[inline]
    fn normalize_moduli(
        self,
        other: BigInt<Digit, DIGIT_BITNESS>,
    ) -> Self::Output {
        let gcd = self.gcd(&other);
        (
            unsafe { self.checked_div(&gcd).unwrap_unchecked() },
            unsafe { other.checked_div(gcd).unwrap_unchecked() },
        )
    }
}

impl<Digit, const DIGIT_BITNESS: usize> NormalizeModuli
    for &BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a BigInt<Digit, DIGIT_BITNESS>: CheckedDiv<Output = Option<BigInt<Digit, DIGIT_BITNESS>>>
        + CheckedDiv<
            BigInt<Digit, DIGIT_BITNESS>,
            Output = Option<BigInt<Digit, DIGIT_BITNESS>>,
        > + Gcd<Output = BigInt<Digit, DIGIT_BITNESS>>,
{
    type Output = (BigInt<Digit, DIGIT_BITNESS>, BigInt<Digit, DIGIT_BITNESS>);

    #[inline]
    fn normalize_moduli(self, other: Self) -> Self::Output {
        let gcd = self.gcd(other);
        (
            unsafe { self.checked_div(&gcd).unwrap_unchecked() },
            unsafe { other.checked_div(gcd).unwrap_unchecked() },
        )
    }
}

macro_rules! integer_normalize_moduli_impl {
    ($($integer:ty)*) => ($(
        impl NormalizeModuli for $integer {
            type Output = (Self, Self);

            #[inline]
            fn normalize_moduli(self, other: Self) -> Self::Output {
                let gcd = self.gcd(other);
                (
                    unsafe { self.checked_div(gcd).unwrap_unchecked() },
                    unsafe { other.checked_div(gcd).unwrap_unchecked() },
                )
            }
        }
    )*)
}

integer_normalize_moduli_impl!(
    i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize
);

pub trait NormalizeSign<Other = Self> {
    type Output;

    fn normalize_sign(self, other: Other) -> Self::Output;
}

impl<Digit, const DIGIT_BITNESS: usize> NormalizeSign
    for BigInt<Digit, DIGIT_BITNESS>
where
    for<'a> &'a Self: Signed,
    Self: Neg<Output = Self>,
{
    type Output = (Self, Self);

    #[inline]
    fn normalize_sign(self, other: Self) -> Self::Output {
        if (&other).is_negative() {
            (-self, -other)
        } else {
            (self, other)
        }
    }
}

macro_rules! signed_integer_normalize_sign_impl {
    ($($integer:ty)*) => ($(
        impl NormalizeSign for $integer {
            type Output = (Self, Self);

            #[inline]
            fn normalize_sign(self, other: Self) -> Self::Output {
                if other.is_negative() {
                    (-self, -other)
                } else {
                    (self, other)
                }
            }
        }
    )*)
}

signed_integer_normalize_sign_impl!(i8 i16 i32 i64 i128 isize);

macro_rules! unsigned_integer_normalize_sign_impl {
    ($($integer:ty)*) => ($(
        impl NormalizeSign for $integer {
            type Output = (Self, Self);

            #[inline(always)]
            fn normalize_sign(self, other: Self) -> Self::Output {
                (self, other)
            }
        }
    )*)
}

unsigned_integer_normalize_sign_impl!(u8 u16 u32 u64 u128 usize);

#[derive(Clone, Copy, Eq, PartialEq)]
pub enum FromFloatConstructionError {
    Infinity,
    NaN,
    OutOfBounds,
}

impl FromFloatConstructionError {
    fn description(self) -> &'static str {
        match self {
            FromFloatConstructionError::Infinity => {
                "Construction from infinity is undefined."
            }
            FromFloatConstructionError::NaN => {
                "Construction from NaN is undefined."
            }
            FromFloatConstructionError::OutOfBounds => {
                "Value is out of bounds."
            }
        }
    }
}

impl fmt::Debug for FromFloatConstructionError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(self.description())
    }
}

impl fmt::Display for FromFloatConstructionError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> std::fmt::Result {
        fmt::Display::fmt(&self.description(), formatter)
    }
}
