//! Implements methods to write a vector type to a slice.

macro_rules! impl_slice_write_to_slice {
    ([$elem_ty:ident; $elem_count:expr]: $id:ident | $test_tt:tt) => {
        impl $id {
            /// Writes the values of the vector to the `slice`.
            ///
            /// # Panics
            ///
            /// If `slice.len() < Self::lanes()` or `&slice[0]` is not
            /// aligned to an `align_of::<Self>()` boundary.
            #[inline]
            pub fn write_to_slice_aligned(self, slice: &mut [$elem_ty]) {
                unsafe {
                    assert!(slice.len() >= $elem_count);
                    let target_ptr =
                        slice.get_unchecked_mut(0) as *mut $elem_ty;
                    assert_eq!(
                        target_ptr
                            .align_offset(crate::mem::align_of::<Self>()),
                        0
                    );
                    self.write_to_slice_aligned_unchecked(slice);
                }
            }

            /// Writes the values of the vector to the `slice`.
            ///
            /// # Panics
            ///
            /// If `slice.len() < Self::lanes()`.
            #[inline]
            pub fn write_to_slice_unaligned(self, slice: &mut [$elem_ty]) {
                unsafe {
                    assert!(slice.len() >= $elem_count);
                    self.write_to_slice_unaligned_unchecked(slice);
                }
            }

            /// Writes the values of the vector to the `slice`.
            ///
            /// # Precondition
            ///
            /// If `slice.len() < Self::lanes()` or `&slice[0]` is not
            /// aligned to an `align_of::<Self>()` boundary, the behavior is
            /// undefined.
            #[inline]
            pub unsafe fn write_to_slice_aligned_unchecked(
                self, slice: &mut [$elem_ty],
            ) {
                debug_assert!(slice.len() >= $elem_count);
                let target_ptr = slice.get_unchecked_mut(0) as *mut $elem_ty;
                debug_assert_eq!(
                    target_ptr.align_offset(crate::mem::align_of::<Self>()),
                    0
                );

                                #[cfg_attr(feature = "cargo-clippy",
                                           allow(clippy::cast_ptr_alignment))]
                        #[cfg_attr(
                            feature = "cargo-clippy",
                            allow(clippy::cast_ptr_alignment)
                        )]
                #[cfg_attr(
                    feature = "cargo-clippy",
                    allow(clippy::cast_ptr_alignment)
                )]
                #[cfg_attr(
                    feature = "cargo-clippy",
                    allow(clippy::cast_ptr_alignment)
                )]
                *(target_ptr as *mut Self) = self;
            }

            /// Writes the values of the vector to the `slice`.
            ///
            /// # Precondition
            ///
            /// If `slice.len() < Self::lanes()` the behavior is undefined.
            #[inline]
            pub unsafe fn write_to_slice_unaligned_unchecked(
                self, slice: &mut [$elem_ty],
            ) {
                debug_assert!(slice.len() >= $elem_count);
                let target_ptr =
                    slice.get_unchecked_mut(0) as *mut $elem_ty as *mut u8;
                let self_ptr = &self as *const Self as *const u8;
                crate::ptr::copy_nonoverlapping(
                    self_ptr,
                    target_ptr,
                    crate::mem::size_of::<Self>(),
                );
            }
        }

        test_if! {
            $test_tt:
            paste::item! {
                pub mod [<$id _slice_write_to_slice>] {
                    use super::*;
                    use crate::iter::Iterator;

                    #[cfg_attr(not(target_arch = "wasm32"), test)]
                    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
                    fn write_to_slice_unaligned() {
                        let mut unaligned = [0 as $elem_ty; $id::lanes() + 1];
                        let vec = $id::splat(42 as $elem_ty);
                        vec.write_to_slice_unaligned(&mut unaligned[1..]);
                        for (index, &b) in unaligned.iter().enumerate() {
                            if index == 0 {
                                assert_eq!(b, 0 as $elem_ty);
                            } else {
                                assert_eq!(b, 42 as $elem_ty);
                                assert_eq!(b, vec.extract(index - 1));
                            }
                        }
                    }

                    // FIXME: wasm-bindgen-test does not support #[should_panic]
                    // #[cfg_attr(not(target_arch = "wasm32"), test)]
                    // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
                    #[cfg(not(target_arch = "wasm32"))]
                    #[test]
                    #[should_panic]
                    fn write_to_slice_unaligned_fail() {
                        let mut unaligned = [0 as $elem_ty; $id::lanes() + 1];
                        let vec = $id::splat(42 as $elem_ty);
                        vec.write_to_slice_unaligned(&mut unaligned[2..]);
                    }

                    union A {
                        data: [$elem_ty; 2 * $id::lanes()],
                        _vec: $id,
                    }

                    #[cfg_attr(not(target_arch = "wasm32"), test)]
                    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
                    fn write_to_slice_aligned() {
                        let mut aligned = A {
                            data: [0 as $elem_ty; 2 * $id::lanes()],
                        };
                        let vec = $id::splat(42 as $elem_ty);
                        unsafe {
                            vec.write_to_slice_aligned(
                                &mut aligned.data[$id::lanes()..]
                            );
                            for (idx, &b) in aligned.data.iter().enumerate() {
                                if idx < $id::lanes() {
                                    assert_eq!(b, 0 as $elem_ty);
                                } else {
                                    assert_eq!(b, 42 as $elem_ty);
                                    assert_eq!(
                                        b, vec.extract(idx - $id::lanes())
                                    );
                                }
                            }
                        }
                    }

                    // FIXME: wasm-bindgen-test does not support #[should_panic]
                    // #[cfg_attr(not(target_arch = "wasm32"), test)]
                    // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
                    #[cfg(not(target_arch = "wasm32"))]
                    #[test]
                    #[should_panic]
                    fn write_to_slice_aligned_fail_lanes() {
                        let mut aligned = A {
                            data: [0 as $elem_ty; 2 * $id::lanes()],
                        };
                        let vec = $id::splat(42 as $elem_ty);
                        unsafe {
                            vec.write_to_slice_aligned(
                                &mut aligned.data[2 * $id::lanes()..]
                            )
                        };
                    }

                    // FIXME: wasm-bindgen-test does not support #[should_panic]
                    // #[cfg_attr(not(target_arch = "wasm32"), test)]
                    // #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
                    #[cfg(not(target_arch = "wasm32"))]
                    #[test]
                    #[should_panic]
                    fn write_to_slice_aligned_fail_align() {
                        unsafe {
                            let mut aligned = A {
                                data: [0 as $elem_ty; 2 * $id::lanes()],
                            };

                            // get a pointer to the front of data
                            let ptr: *mut $elem_ty
                                = aligned.data.as_mut_ptr() as *mut $elem_ty;
                            // offset pointer by one element
                            let ptr = ptr.wrapping_add(1);

                            if ptr.align_offset(crate::mem::align_of::<$id>())
                                == 0 {
                                // the pointer is properly aligned, so
                                // write_to_slice_aligned won't fail here (e.g.
                                // this can happen for i128x1). So we panic to
                                // make the "should_fail" test pass:
                                panic!("ok");
                            }

                            // create a slice - this is safe, because the
                            // elements of the slice exist, are properly
                            // initialized, and properly aligned:
                            let s: &mut [$elem_ty]
                                = slice::from_raw_parts_mut(ptr, $id::lanes());
                            // this should always panic because the slice
                            // alignment does not match the alignment
                            // requirements for the vector type:
                            let vec = $id::splat(42 as $elem_ty);
                            vec.write_to_slice_aligned(s);
                        }
                    }
                }
            }
        }
    };
}
