#[derive(Clone, Debug)]
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct Zip<T> {
    t: T,
}

pub fn multizip<T, U>(t: U) -> Zip<T> where Zip<T>: From<U>,
                                            Zip<T>: Iterator,
{
    Zip::from(t)
}

macro_rules! impl_zip_iter {
    ($($B:ident),*) => (
        #[allow(non_snake_case)]
        impl<$($B: IntoIterator),*> From<($($B,)*)> for Zip<($($B::IntoIter,)*)> {
            fn from(t: ($($B,)*)) -> Self {
                let ($($B,)*) = t;
                Zip { t: ($($B.into_iter(),)*) }
            }
        }

        #[allow(non_snake_case)]
        #[allow(unused_assignments)]
        impl<$($B),*> Iterator for Zip<($($B,)*)>
            where $($B: Iterator,)*
        {
            type Item = ($($B::Item,)*);
            fn next(&mut self) -> Option<Self::Item>
            {
                let ($(ref mut $B,)*) = self.t;
                $(
                    let $B = match $B.next() {
                        None => return None,
                        Some(elt) => elt
                    };
                )*
                Some(($($B,)*))
            }
        }

        #[allow(non_snake_case)]
        impl<$($B),*> ExactSizeIterator for Zip<($($B,)*)>
            where $($B: ExactSizeIterator,)*
        { }

        #[allow(non_snake_case)]
        impl<$($B),*> DoubleEndedIterator for Zip<($($B,)*)>
            where $($B: DoubleEndedIterator + ExactSizeIterator,)*
        {
            #[inline]
            fn next_back(&mut self) -> Option<Self::Item> {
                let ($(ref mut $B,)*) = self.t;
                let size = *[$( $B.len(), )*].iter().min().unwrap();
                $(
                    if $B.len() != size {
                        for _ in 0..$B.len() - size { $B.next_back(); }
                    }
                )*
                match ($($B.next_back(),)*) {
                    ($(Some($B),)*) => Some(($($B,)*)),
                    _ => None,
                }
            }
        }
    );
}

impl_zip_iter!(A);
impl_zip_iter!(A, B);
impl_zip_iter!(A, B, C);
impl_zip_iter!(A, B, C, D);
impl_zip_iter!(A, B, C, D, E);
impl_zip_iter!(A, B, C, D, E, F);
impl_zip_iter!(A, B, C, D, E, F, G);
impl_zip_iter!(A, B, C, D, E, F, G, H);
impl_zip_iter!(A, B, C, D, E, F, G, H, I);
impl_zip_iter!(A, B, C, D, E, F, G, H, I, J);
impl_zip_iter!(A, B, C, D, E, F, G, H, I, J, K);
impl_zip_iter!(A, B, C, D, E, F, G, H, I, J, K, L);


#[cfg(test)]
mod tests {
    use crate::alpha::multizip_test::multizip;

    #[test]
    fn test_multi_zip() {

// iterate over three sequences side-by-side
        let mut results = [0, 0, 0, 0];
        let inputs = [3, 7, 9, 6];
        for (r, index, input) in multizip((&mut results, 0..10, &inputs)) {
            *r = index * 10 + input;
        }
        assert_eq!(results, [0 + 3, 10 + 7, 29, 36]);
    }
}