use std::borrow::{Borrow, BorrowMut};

use pas::{Slice, SliceMut};

#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
    pub position: [u32; 3],
    pub uv: [u32; 2],
}

pub fn data() -> Vec<Vertex> {
    vec![
        Vertex {
            position: [0, 1, 2],
            uv: [3, 4],
        },
        Vertex {
            position: [5, 6, 7],
            uv: [8, 9],
        },
        Vertex {
            position: [10, 11, 12],
            uv: [13, 14],
        },
    ]
}

// Test for [`Slice`] and [`SliceMut`] as well as [`SliceIterator`] and [`SliceIteratorMut`].
macro_rules! tests {
    ($slice: ident, $name: ident, $borrow: ident) => { paste::expr! {
        #[test]
        fn [<slice_len_$name>]() {
            #[allow(unused_mut)]
            let mut vertices = data();

            #[allow(unused_mut)]
            let mut empty: Vec<u32> = Vec::new();
            let slice: $slice<u32> = $slice::new(empty.$borrow(), 0);
            assert_eq!(slice.len(), 0);

            let slice: $slice<f32> = $slice::new(vertices.$borrow(), 0);
            assert_eq!(slice.len(), 3);

            let slice: $slice<[f32; 2]> = $slice::new(vertices.$borrow(), std::mem::size_of::<[f32; 3]>());
            assert_eq!(slice.len(), 3);

            let slice: $slice<[f32; 2]> = $slice::strided(vertices.$borrow(), std::mem::size_of::<[f32; 3]>(), 2);
            assert_eq!(slice.len(), 2);
        }

        #[test]
        fn [<strided_$name>]() {
            #[allow(unused_mut)]
            let mut data: [u32; 12] = [
                1, 2, 3,
                4, 5, 6,
                7, 8, 9,
                10, 11, 12,
            ];
            let stride: usize = 6;

            let positions: $slice<[u32; 3]> = $slice::strided(data.$borrow(), 0, stride);
            assert!(positions.iter().eq([[1, 2, 3], [7, 8, 9]].iter()));

            let normals: $slice<[u32; 3]> = $slice::strided(data.$borrow(), 3 * std::mem::size_of::<u32>(), stride);
            assert!(normals.iter().eq([[4, 5, 6], [10, 11, 12]].iter()));
        }

        #[test]
        fn [<indexing_$name>]() {
            #[allow(unused_mut)]
            let mut vertices = data();
            let slice: $slice<[u32; 3]> = $slice::new(vertices.$borrow(), 0);
            assert_eq!(*slice.get(0).unwrap(), [0_u32, 1, 2]);
            assert_eq!(*slice.get(1).unwrap(), [5_u32, 6, 7]);
            assert_eq!(slice[0], [0, 1, 2]);
            assert_eq!(slice[1], [5, 6, 7]);

            // Point to third uv
            let slice: $slice<[u32; 2]> = $slice::new(vertices.$borrow(),
                2 * std::mem::size_of::<Vertex>() + std::mem::size_of::<[f32; 3]>());
            assert_eq!(slice[0], [13, 14]);
            assert_eq!(slice.get(1), None);
        }

        #[test]
        fn [<iter_$name>]() {
            #[allow(unused_mut)]
            let mut vertices = data();

            let slice: $slice<[u32; 3]> = $slice::new(vertices.$borrow(), 0);
            let mut iter = slice.iter();
            assert_eq!(*iter.next().unwrap(), [0, 1, 2]);
            assert_eq!(*iter.next().unwrap(), [5, 6, 7]);
            assert_eq!(*iter.next().unwrap(), [10, 11, 12]);
            assert_eq!(iter.next(), None);

            let slice: $slice<[u32; 2]> = $slice::new(vertices.$borrow(), std::mem::size_of::<[f32; 3]>());
            let mut iter = slice.iter();
            assert_eq!(*iter.next().unwrap(), [3, 4]);
            assert_eq!(*iter.next().unwrap(), [8, 9]);
            assert_eq!(*iter.next().unwrap(), [13, 14]);
            assert_eq!(iter.next(), None);

            // nth
            let mut iter = slice.iter();
            assert_eq!(iter.nth(4), None);
            let mut iter = slice.iter();
            assert_eq!(iter.nth(0), Some([3, 4].$borrow()));
            assert_eq!(iter.nth(0), Some([3, 4].$borrow()));
            assert_eq!(iter.nth(2), Some([13, 14].$borrow()));
            assert_eq!(iter.nth(0), Some([13, 14].$borrow()));
        }

        #[test]
        #[should_panic]
        fn [<attr_larger_than_stride_$name>]() {
            #[allow(unused_mut)]
            let mut positions = [0, 1, 2, 3];
            let _: $slice<Vertex> = $slice::new(positions.$borrow(), 0);
        }

        #[test]
        #[should_panic]
        fn [<offset_larger_than_slice_$name>]() {
            #[allow(unused_mut)]
            let mut vertices = data();
            let bytes = std::mem::size_of_val(&*vertices);
            let _: $slice<[u32; 3]> = $slice::new(vertices.$borrow(), bytes);
        }

        #[test]
        #[should_panic]
        fn [<unaligned_attr_$name>]() {
            #[allow(unused_mut)]
            let mut positions = [0, 1, 2, 3];
            let _: $slice<u32> = $slice::new(positions.$borrow(), 1);
        }
    }};
}

tests!(Slice, immutable, borrow);
tests!(SliceMut, mutable, borrow_mut);