use core::{mem::{MaybeUninit, transmute}, ops::{Add, Sub}};
use cc_traits::{Collection, PopBack, PopFront, PushBack};
use num_traits::{ConstOne, ConstZero, bounds::UpperBounded, ops::overflowing::OverflowingSub};

pub struct Suber<I: Iterator, J: Iterator, B: Collection, C: Copy = <B as Collection>::Item> where B::Item: Copy {
    buffer: B, 
    first: I, 
    second: J, 
    carry: C, 
    pub overflow: bool
}
impl<I: Iterator, J: Iterator, B: Collection, C: Copy> Suber<I, J, B, C> where B::Item: Copy {
    pub const fn new (first: I, second: J, buffer: B) -> Self where C: ConstZero {
        Self {
            buffer: buffer, 
            first: first, 
            second: second, 
            carry: C::ZERO, 
            overflow: unsafe{transmute(MaybeUninit::<bool>::uninit())}
        }
    }
}

impl<I: Iterator, J: Iterator, B: Collection, C: Copy> Iterator for Suber<I, J, B, C> where
I::Item: Into<B::Item>, 
J::Item: Into<B::Item>, 
B: PopBack + PushBack + PopFront, 
B::Item: OverflowingSub + Sub + ConstOne + UpperBounded + PartialEq + Copy, 
C: Into<B::Item> + From<bool> + ConstZero + Add<Output = C>
{
    type Item = B::Item;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(limb) = self.buffer.pop_front() {
            return Some(limb);
        }
        loop {
            match (self.first.next(), self.second.next()) {
                (Some(first), Some(second)) => {
                    let (tmp1, carry1)
                    = first.into().overflowing_sub(&second.into());
                    let (tmp2, carry2)
                    = tmp1.overflowing_sub(&self.carry.into());
                    let (limb, carry3)
                    = tmp2.overflowing_sub(&B::Item::ONE);
                    self.carry = C::from(carry1) + C::from(carry2) + C::from(carry3);
                    if self.carry.is_zero() {
                        return Some(limb);
                    }
                    self.buffer.push_back(limb);
                }
                (Some(first), None) => {
                    let (limb, carry)
                    = first.into().overflowing_sub(&self.carry.into());
                    self.carry = carry.into();
                    if self.carry.is_zero () {
                        return Some(limb);
                    }
                    self.buffer.push_back(limb);
                }
                (None, Some(_second)) => {
                    self.overflow = true;
                    return None;
                }
                (None, None) => {
                    if self.carry.is_zero() {
                        self.overflow = false;
                        return self.buffer.pop_front();
                    } else {
                        while let Some(limb) = self.buffer.pop_back() {
                            if limb == B::Item::max_value() {
                                self.carry = C::ZERO;
                                return self.buffer.pop_front();
                            }
                            else if limb == B::Item::max_value() - B::Item::ONE {continue;}
                            else {
                                break;
                            }
                        }
                        self.overflow = true;
                        return None;
                    }
                }
            }
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        (0, self.first.size_hint().1)
    }
}

#[macro_export]
macro_rules! sub {
    ($first: expr, $second: expr, $buffer: expr) => {
        {
            let iter = $crate::Suber::new(
                IntoIterator::into_iter($first), 
                IntoIterator::into_iter($second), 
                $buffer
            );
            let overflow = iter.overflow;
            (iter.collect(), overflow)
        }
    };
    ($first: expr, $second: expr, $buffer: expr, $carry: ty) => {
        {
            let iter = $crate::Suber::<_, _, _, $carry>::new(
                IntoIterator::into_iter($first), 
                IntoIterator::into_iter($second), 
                $buffer
            );
            let overflow = iter.overflow;
            (iter.collect(), overflow)
        }
    }
}
