macro_rules! impl_container_write {
    (
        $ty:ident<$($a:lifetime,)? T $(: ?$tbound0:ident $(+ $tbound1:ident + $tbound2:lifetime)?)?>
        $(=> $f:ident)?
    ) => {
        impl<$($a,)? Ctx, Tag, T> $crate::BitEncode<Ctx, Tag> for $ty<$($a,)? T>
        where
            T: $crate::BitEncode<Ctx, Tag> $(+ ?$tbound0 $(+ $tbound1 + $tbound2)?)?,
        {
            fn encode<W, E>(
                &self,
                write: &mut W,
                ctx: &mut Ctx,
                tag: Tag,
            ) -> $crate::Result<()>
            where
                W: ::bitstream_io::BitWrite,
                E: ::bitstream_io::Endianness,
            {
                use core::ops::Deref;

                $crate::BitEncode::encode::<_, E>(
                    self $(.$f()?)? .deref(),
                    write,
                    ctx,
                    tag,
                )
            }
        }
    };
}

#[allow(unused)]
macro_rules! impl_container_read {
    ($ty:ident<T $(: ?$tbound:ident)?>) => {
        impl<Ctx, Tag, T> $crate::BitDecode<Ctx, Tag> for $ty<T>
        where
            T: $crate::BitDecode<Ctx, Tag> $(+ ?$tbound)?,
        {
            fn decode<R, E>(read: &mut R, ctx: &mut Ctx, tag: Tag) -> $crate::Result<Self>
            where
                R: ::bitstream_io::BitRead,
                E: ::bitstream_io::Endianness,
            {
                Ok($ty::new($crate::BitDecode::decode::<_, E>(read, ctx, tag)?))
            }
        }
    };
}

#[cfg(feature = "alloc")]
mod box_ {
    use alloc::boxed::Box;

    impl_container_write!(Box<T: ?Sized>);
    impl_container_read!(Box<T: ?Sized>);
    test_codec!(Box<u8>; Box::new(1) => [0x01]);
    test_roundtrip!(Box<u8>);
}

#[cfg(feature = "alloc")]
mod rc {
    use alloc::rc::Rc;

    impl_container_write!(Rc<T: ?Sized>);
    impl_container_read!(Rc<T: ?Sized>);
    test_codec!(Rc<u8>; Rc::new(1) => [0x01]);
    test_roundtrip!(Rc<u8>);
}

#[cfg(feature = "alloc")]
mod arc {
    use alloc::sync::Arc;

    impl_container_write!(Arc<T: ?Sized>);
    impl_container_read!(Arc<T: ?Sized>);
    test_codec!(Arc<u8>; Arc::new(1) => [0x01]);
    test_roundtrip!(Arc<u8>);
}

#[cfg(feature = "alloc")]
mod cow {
    use alloc::borrow::{Cow, ToOwned};

    impl_container_write!(Cow<'a, T: ?Sized + ToOwned + 'a>);
    test_encode!(Cow<u8>; Cow::Owned(1) => [0x01]);
}

mod cell {
    use core::cell::Cell;

    use bitstream_io::{BitWrite, Endianness};

    use crate::{BitEncode, Result};

    impl<Ctx, Tag, T> BitEncode<Ctx, Tag> for Cell<T>
    where
        T: BitEncode<Ctx, Tag> + Copy,
    {
        fn encode<W, E>(&self, write: &mut W, ctx: &mut Ctx, tag: Tag) -> Result<()>
        where
            W: BitWrite,
            E: Endianness,
        {
            self.get().encode::<_, E>(write, ctx, tag)
        }
    }

    test_encode!(Cell<u8>; Cell::new(1) => [0x01]);
}

#[cfg(feature = "std")]
mod rwlock {
    use std::sync::RwLock;

    impl_container_write!(RwLock<T: ?Sized> => read);

    #[cfg(test)]
    mod tests {
        use alloc::vec::Vec;

        use bitstream_io::{BigEndian, BitWriter};

        use crate::BitEncode;

        use super::*;

        #[test]
        fn encode() {
            let mut buffer: Vec<u8> = Vec::new();
            BitEncode::encode::<_, BigEndian>(
                &RwLock::new(1u8),
                &mut BitWriter::endian(&mut buffer, BigEndian),
                &mut (),
                (),
            )
            .unwrap();
            assert_eq!([1], *buffer);
        }
    }
}

#[cfg(feature = "std")]
mod mutex {
    use std::sync::Mutex;

    impl_container_write!(Mutex<T: ?Sized> => lock);
    test_encode!(Mutex<u8>; Mutex::new(1) => [0x01]);
}

mod ref_cell {
    use core::cell::RefCell;

    impl_container_write!(RefCell<T: ?Sized> => try_borrow);
    test_encode!(RefCell<u8>; RefCell::new(1) => [0x01]);
}
