// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

// This way we can copy-paste Yokeable impls
#![allow(unknown_lints)] // forgetting_copy_types
#![allow(renamed_and_removed_lints)] // forgetting_copy_types
#![allow(forgetting_copy_types)]
#![allow(clippy::forget_copy)]
#![allow(clippy::forget_non_drop)]

use crate::flexzerovec::FlexZeroVec;
use crate::map::ZeroMapBorrowed;
use crate::map::ZeroMapKV;
use crate::map2d::ZeroMap2dBorrowed;
use crate::ule::*;
use crate::{VarZeroVec, ZeroMap, ZeroMap2d, ZeroVec};
use core::{mem, ptr};
use yoke::*;

// This impl is similar to the impl on Cow and is safe for the same reasons
/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
unsafe impl<'a, T: 'static + AsULE + ?Sized> Yokeable<'a> for ZeroVec<'static, T> {
    type Output = ZeroVec<'a, T>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        self
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        self
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

// This impl is similar to the impl on Cow and is safe for the same reasons
/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
unsafe impl<'a, T: 'static + VarULE + ?Sized> Yokeable<'a> for VarZeroVec<'static, T> {
    type Output = VarZeroVec<'a, T>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        self
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        self
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

// This impl is similar to the impl on Cow and is safe for the same reasons
/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
unsafe impl<'a> Yokeable<'a> for FlexZeroVec<'static> {
    type Output = FlexZeroVec<'a>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        self
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        self
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
#[allow(clippy::transmute_ptr_to_ptr)]
unsafe impl<'a, K, V> Yokeable<'a> for ZeroMap<'static, K, V>
where
    K: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    V: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    <K as ZeroMapKV<'static>>::Container: for<'b> Yokeable<'b>,
    <V as ZeroMapKV<'static>>::Container: for<'b> Yokeable<'b>,
{
    type Output = ZeroMap<'a, K, V>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        unsafe {
            // Unfortunately, because K and V are generic, rustc is
            // unaware that these are covariant types, and cannot perform this cast automatically.
            // We transmute it instead, and enforce the lack of a lifetime with the `K, V: 'static` bound
            mem::transmute::<&Self, &Self::Output>(self)
        }
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        unsafe {
            // Similar problem as transform(), but we need to use ptr::read since
            // the compiler isn't sure of the sizes
            let this = mem::ManuallyDrop::new(self);
            let ptr: *const Self::Output = (&*this as *const Self).cast();
            ptr::read(ptr)
        }
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
#[allow(clippy::transmute_ptr_to_ptr)]
unsafe impl<'a, K, V> Yokeable<'a> for ZeroMapBorrowed<'static, K, V>
where
    K: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    V: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    &'static <K as ZeroMapKV<'static>>::Slice: for<'b> Yokeable<'b>,
    &'static <V as ZeroMapKV<'static>>::Slice: for<'b> Yokeable<'b>,
{
    type Output = ZeroMapBorrowed<'a, K, V>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        unsafe {
            // Unfortunately, because K and V are generic, rustc is
            // unaware that these are covariant types, and cannot perform this cast automatically.
            // We transmute it instead, and enforce the lack of a lifetime with the `K, V: 'static` bound
            mem::transmute::<&Self, &Self::Output>(self)
        }
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        unsafe {
            // Similar problem as transform(), but we need to use ptr::read since
            // the compiler isn't sure of the sizes
            let this = mem::ManuallyDrop::new(self);
            let ptr: *const Self::Output = (&*this as *const Self).cast();
            ptr::read(ptr)
        }
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
#[allow(clippy::transmute_ptr_to_ptr)]
unsafe impl<'a, K0, K1, V> Yokeable<'a> for ZeroMap2d<'static, K0, K1, V>
where
    K0: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    K1: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    V: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    <K0 as ZeroMapKV<'static>>::Container: for<'b> Yokeable<'b>,
    <K1 as ZeroMapKV<'static>>::Container: for<'b> Yokeable<'b>,
    <V as ZeroMapKV<'static>>::Container: for<'b> Yokeable<'b>,
{
    type Output = ZeroMap2d<'a, K0, K1, V>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        unsafe {
            // Unfortunately, because K and V are generic, rustc is
            // unaware that these are covariant types, and cannot perform this cast automatically.
            // We transmute it instead, and enforce the lack of a lifetime with the `K0, K1, V: 'static` bound
            mem::transmute::<&Self, &Self::Output>(self)
        }
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        unsafe {
            // Similar problem as transform(), but we need to use ptr::read since
            // the compiler isn't sure of the sizes
            let this = mem::ManuallyDrop::new(self);
            let ptr: *const Self::Output = (&*this as *const Self).cast();
            ptr::read(ptr)
        }
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

/// This impl requires enabling the optional `yoke` Cargo feature of the `zerovec` crate
#[allow(clippy::transmute_ptr_to_ptr)]
unsafe impl<'a, K0, K1, V> Yokeable<'a> for ZeroMap2dBorrowed<'static, K0, K1, V>
where
    K0: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    K1: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    V: 'static + for<'b> ZeroMapKV<'b> + ?Sized,
    &'static <K0 as ZeroMapKV<'static>>::Slice: for<'b> Yokeable<'b>,
    &'static <K1 as ZeroMapKV<'static>>::Slice: for<'b> Yokeable<'b>,
    &'static <V as ZeroMapKV<'static>>::Slice: for<'b> Yokeable<'b>,
{
    type Output = ZeroMap2dBorrowed<'a, K0, K1, V>;
    #[inline]
    fn transform(&'a self) -> &'a Self::Output {
        unsafe {
            // Unfortunately, because K and V are generic, rustc is
            // unaware that these are covariant types, and cannot perform this cast automatically.
            // We transmute it instead, and enforce the lack of a lifetime with the `K0, K1, V: 'static` bound
            mem::transmute::<&Self, &Self::Output>(self)
        }
    }
    #[inline]
    fn transform_owned(self) -> Self::Output {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        unsafe {
            // Similar problem as transform(), but we need to use ptr::read since
            // the compiler isn't sure of the sizes
            let this = mem::ManuallyDrop::new(self);
            let ptr: *const Self::Output = (&*this as *const Self).cast();
            ptr::read(ptr)
        }
    }
    #[inline]
    unsafe fn make(from: Self::Output) -> Self {
        debug_assert!(mem::size_of::<Self::Output>() == mem::size_of::<Self>());
        let from = mem::ManuallyDrop::new(from);
        let ptr: *const Self = (&*from as *const Self::Output).cast();
        ptr::read(ptr)
    }
    #[inline]
    fn transform_mut<F>(&'a mut self, f: F)
    where
        F: 'static + for<'b> FnOnce(&'b mut Self::Output),
    {
        unsafe { f(mem::transmute::<&mut Self, &mut Self::Output>(self)) }
    }
}

#[cfg(test)]
#[allow(non_camel_case_types, non_snake_case)]
mod test {
    use super::*;
    use crate::{vecs::FlexZeroSlice, VarZeroSlice, ZeroSlice};
    use databake::*;

    // Note: The following derives cover Yoke as well as Serde and databake. These may partially
    // duplicate tests elsewhere in this crate, but they are here for completeness.

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_ZeroVec<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroVec<'data, u16>,
    }

    #[test]
    #[ignore] // https://github.com/rust-lang/rust/issues/98906
    fn bake_ZeroVec() {
        test_bake!(
            DeriveTest_ZeroVec<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroVec {
                _data: crate::ZeroVec::new(),
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_ZeroSlice<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: &'data ZeroSlice<u16>,
    }

    #[test]
    fn bake_ZeroSlice() {
        test_bake!(
            DeriveTest_ZeroSlice<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroSlice {
                _data: crate::ZeroSlice::new_empty(),
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_FlexZeroVec<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: FlexZeroVec<'data>,
    }

    #[test]
    fn bake_FlexZeroVec() {
        test_bake!(
            DeriveTest_FlexZeroVec<'static>,
            crate::yoke_impls::test::DeriveTest_FlexZeroVec {
                _data: crate::vecs::FlexZeroVec::new(),
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_FlexZeroSlice<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: &'data FlexZeroSlice,
    }

    #[test]
    fn bake_FlexZeroSlice() {
        test_bake!(
            DeriveTest_FlexZeroSlice<'static>,
            crate::yoke_impls::test::DeriveTest_FlexZeroSlice {
                _data: unsafe { crate::vecs::FlexZeroSlice::from_byte_slice_unchecked(b"\x01\0") },
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_VarZeroVec<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: VarZeroVec<'data, str>,
    }

    #[test]
    fn bake_VarZeroVec() {
        test_bake!(
            DeriveTest_VarZeroVec<'static>,
            crate::yoke_impls::test::DeriveTest_VarZeroVec {
                _data: crate::VarZeroVec::new(),
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    struct DeriveTest_VarZeroSlice<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: &'data VarZeroSlice<str>,
    }

    #[test]
    fn bake_VarZeroSlice() {
        test_bake!(
            DeriveTest_VarZeroSlice<'static>,
            crate::yoke_impls::test::DeriveTest_VarZeroSlice {
                _data: crate::VarZeroSlice::new_empty()
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    #[yoke(prove_covariance_manually)]
    struct DeriveTest_ZeroMap<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroMap<'data, [u8], str>,
    }

    #[test]
    fn bake_ZeroMap() {
        test_bake!(
            DeriveTest_ZeroMap<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroMap {
                _data: unsafe {
                    #[allow(unused_unsafe)]
                    crate::ZeroMap::from_parts_unchecked(
                        crate::VarZeroVec::new(),
                        crate::VarZeroVec::new(),
                    )
                },
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    #[yoke(prove_covariance_manually)]
    struct DeriveTest_ZeroMapBorrowed<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroMapBorrowed<'data, [u8], str>,
    }

    #[test]
    fn bake_ZeroMapBorrowed() {
        test_bake!(
            DeriveTest_ZeroMapBorrowed<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroMapBorrowed {
                _data: unsafe {
                    #[allow(unused_unsafe)]
                    crate::maps::ZeroMapBorrowed::from_parts_unchecked(
                        crate::VarZeroSlice::new_empty(),
                        crate::VarZeroSlice::new_empty(),
                    )
                },
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    #[yoke(prove_covariance_manually)]
    struct DeriveTest_ZeroMapWithULE<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroMap<'data, ZeroSlice<u32>, str>,
    }

    #[test]
    fn bake_ZeroMapWithULE() {
        test_bake!(
            DeriveTest_ZeroMapWithULE<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroMapWithULE {
                _data: unsafe {
                    #[allow(unused_unsafe)]
                    crate::ZeroMap::from_parts_unchecked(
                        crate::VarZeroVec::new(),
                        crate::VarZeroVec::new(),
                    )
                },
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable, zerofrom::ZeroFrom)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    #[yoke(prove_covariance_manually)]
    struct DeriveTest_ZeroMap2d<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroMap2d<'data, u16, u16, str>,
    }

    #[test]
    fn bake_ZeroMap2d() {
        test_bake!(
            DeriveTest_ZeroMap2d<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroMap2d {
                _data: unsafe {
                    #[allow(unused_unsafe)]
                    crate::ZeroMap2d::from_parts_unchecked(
                        crate::ZeroVec::new(),
                        crate::ZeroVec::new(),
                        crate::ZeroVec::new(),
                        crate::VarZeroVec::new(),
                    )
                },
            },
            zerovec,
        );
    }

    #[derive(yoke::Yokeable)]
    #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
    #[cfg_attr(feature = "databake", derive(databake::Bake), databake(path = zerovec::yoke_impls::test))]
    #[yoke(prove_covariance_manually)]
    struct DeriveTest_ZeroMap2dBorrowed<'data> {
        #[cfg_attr(feature = "serde", serde(borrow))]
        _data: ZeroMap2dBorrowed<'data, u16, u16, str>,
    }

    #[test]
    fn bake_ZeroMap2dBorrowed() {
        test_bake!(
            DeriveTest_ZeroMap2dBorrowed<'static>,
            crate::yoke_impls::test::DeriveTest_ZeroMap2dBorrowed {
                _data: unsafe {
                    #[allow(unused_unsafe)]
                    crate::maps::ZeroMap2dBorrowed::from_parts_unchecked(
                        crate::ZeroSlice::new_empty(),
                        crate::ZeroSlice::new_empty(),
                        crate::ZeroSlice::new_empty(),
                        crate::VarZeroSlice::new_empty(),
                    )
                },
            },
            zerovec,
        );
    }
}
