use std::cmp;

// Note: There are different ways to implement ZipSlices.
// This version performed the best in benchmarks.
//
// I also implemented a version with three pointers (tptr, tend, uptr),
// that mimiced slice::Iter and only checked bounds by using tptr == tend,
// but that was inferior to this solution.

/// An iterator which iterates two slices simultaneously.
///
/// `ZipSlices` acts like a double-ended `.zip()` iterator.
///
/// It was intended to be more efficient than `.zip()`, and it was, then
/// rustc changed how it optimizes so it can not promise improved performance
/// at this time.
///
/// Note that elements past the end of the shortest of the two slices are ignored.
///
/// Iterator element type for `ZipSlices<T, U>` is `(T::Item, U::Item)`. For example,
/// for a `ZipSlices<&'a [A], &'b mut [B]>`, the element type is `(&'a A, &'b mut B)`.
#[derive(Clone)]
pub struct ZipSlices<T, U> {
    t: T,
    u: U,
    len: usize,
    index: usize,
}

impl<'a, 'b, A, B> ZipSlices<&'a [A], &'b [B]> {
    /// Create a new `ZipSlices` from slices `a` and `b`.
    ///
    /// Act like a double-ended `.zip()` iterator, but more efficiently.
    ///
    /// Note that elements past the end of the shortest of the two slices are ignored.
    #[inline(always)]
    pub fn new(a: &'a [A], b: &'b [B]) -> Self {
        let minl = cmp::min(a.len(), b.len());
        ZipSlices {
            t: a,
            u: b,
            len: minl,
            index: 0,
        }
    }
}

impl<T, U> ZipSlices<T, U>
where
    T: Slice,
    U: Slice,
{
    /// Create a new `ZipSlices` from slices `a` and `b`.
    ///
    /// Act like a double-ended `.zip()` iterator, but more efficiently.
    ///
    /// Note that elements past the end of the shortest of the two slices are ignored.
    #[inline(always)]
    pub fn from_slices(a: T, b: U) -> Self {
        let minl = cmp::min(a.len(), b.len());
        Self {
            t: a,
            u: b,
            len: minl,
            index: 0,
        }
    }
}

impl<T, U> Iterator for ZipSlices<T, U>
where
    T: Slice,
    U: Slice,
{
    type Item = (T::Item, U::Item);

    #[inline(always)]
    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            if self.index >= self.len {
                None
            } else {
                let i = self.index;
                self.index += 1;
                Some((self.t.get_unchecked(i), self.u.get_unchecked(i)))
            }
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        let len = self.len - self.index;
        (len, Some(len))
    }
}

impl<T, U> DoubleEndedIterator for ZipSlices<T, U>
where
    T: Slice,
    U: Slice,
{
    #[inline(always)]
    fn next_back(&mut self) -> Option<Self::Item> {
        unsafe {
            if self.index >= self.len {
                None
            } else {
                self.len -= 1;
                let i = self.len;
                Some((self.t.get_unchecked(i), self.u.get_unchecked(i)))
            }
        }
    }
}

impl<T, U> ExactSizeIterator for ZipSlices<T, U>
where
    T: Slice,
    U: Slice,
{
}

unsafe impl<T, U> Slice for ZipSlices<T, U>
where
    T: Slice,
    U: Slice,
{
    type Item = (T::Item, U::Item);

    fn len(&self) -> usize {
        self.len - self.index
    }

    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item {
        (self.t.get_unchecked(i), self.u.get_unchecked(i))
    }
}

/// A helper trait to let `ZipSlices` accept both `&[T]` and `&mut [T]`.
///
/// # Safety
///
/// Unsafe trait because:
///
/// - Implementors must guarantee that `get_unchecked` is valid for all indices `0..len()`.
pub unsafe trait Slice {
    /// The type of a reference to the slice's elements
    type Item;
    #[doc(hidden)]
    fn len(&self) -> usize;
    #[doc(hidden)]
    unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item;
}

unsafe impl<'a, T> Slice for &'a [T] {
    type Item = &'a T;
    #[inline(always)]
    fn len(&self) -> usize {
        (**self).len()
    }
    #[inline(always)]
    unsafe fn get_unchecked(&mut self, i: usize) -> &'a T {
        debug_assert!(i < self.len());
        (**self).get_unchecked(i)
    }
}

unsafe impl<'a, T> Slice for &'a mut [T] {
    type Item = &'a mut T;
    #[inline(always)]
    fn len(&self) -> usize {
        (**self).len()
    }
    #[inline(always)]
    unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut T {
        debug_assert!(i < self.len());
        // override the lifetime constraints of &mut &'a mut [T]
        (*(*self as *mut [T])).get_unchecked_mut(i)
    }
}

#[test]
fn zipslices() {
    let xs = [1, 2, 3, 4, 5, 6];
    let ys = [1, 2, 3, 7];
    ::itertools::assert_equal(ZipSlices::new(&xs, &ys), xs.iter().zip(&ys));

    let xs = [1, 2, 3, 4, 5, 6];
    let mut ys = [0; 6];
    for (x, y) in ZipSlices::from_slices(&xs[..], &mut ys[..]) {
        *y = *x;
    }
    ::itertools::assert_equal(&xs, &ys);
}
