#[allow(unused)]
macro_rules! impl_read_list {
    (
        $ty:ident<T $(: $tbound0:ident $(+ $tbound1:ident)?)?
        $(, $h:ident: $hbound0:ident + $hbound1:ident)?>,
        $new:expr,
        $push:ident
    ) => {
        impl<Tag, Ctx, T, $($h)?> $crate::BitDecode<Ctx, $crate::Tag<Tag>> for $ty<T, $($h)?>
        where
            T: $crate::BitDecode<Ctx> $(+ $tbound0 $(+ $tbound1)?)?,
            Tag: ::core::convert::TryInto<usize>,
            $($h: $hbound0 + $hbound1)?
        {
            fn decode<R, E>(
                read: &mut R,
                ctx: &mut Ctx,
                tag: $crate::Tag<Tag>,
            ) -> $crate::Result<Self>
            where
                R: ::bitstream_io::BitRead,
                E: ::bitstream_io::Endianness,
            {
                let item_count = ::core::convert::TryInto::try_into(tag.0)
                    .map_err(|_| $crate::Error::TagConvert)?;
                let mut this = ($new)(item_count);
                for _ in 0..item_count {
                    this.$push($crate::BitDecode::<_, _>::decode::<_, E>(read, ctx, ())?);
                }
                ::core::result::Result::Ok(this)
            }
        }

        impl<Ctx, T, $($h)?> $crate::BitDecode<Ctx, $crate::Untagged> for $ty<T, $($h)?>
        where
            T: $crate::BitDecode<Ctx> $(+ $tbound0 $(+ $tbound1)?)?,
            $($h: $hbound0 + $hbound1)?
        {
            fn decode<R, E>(
                read: &mut R,
                ctx: &mut Ctx,
                _: $crate::Untagged,
            ) -> $crate::Result<Self>
            where
                R: ::bitstream_io::BitRead,
                E: ::bitstream_io::Endianness,
            {
                $crate::util::decode_items_to_eof::<_, E, _, _>(read, ctx).collect()
            }
        }

        #[cfg(feature = "prepend-tags")]
        impl<Ctx, T, $($h)?> $crate::BitDecode<Ctx> for $ty<T, $($h)?>
        where
            T: $crate::BitDecode<Ctx> $(+ $tbound0 $(+ $tbound1)?)?,
            $($h: $hbound0 + $hbound1)?
        {
            fn decode<R, E>(
                read: &mut R,
                ctx: &mut Ctx,
                (): (),
            ) -> $crate::Result<Self>
            where
                R: ::bitstream_io::BitRead,
                E: ::bitstream_io::Endianness,
            {
                let tag: usize = $crate::BitDecode::decode::<_, E>(read, ctx, ())?;
                $crate::BitDecode::decode::<_, E>(read, ctx, $crate::Tag(tag))
            }
        }
    }
}

#[allow(unused)]
macro_rules! impl_write_list {
    ($ty:ident<T $(: $tbound0:ident $(+ $tbound1:ident)?)? $(, $h:ident)?> ) => {
        impl<Ctx, T, $($h)?> $crate::BitEncode<Ctx, $crate::Untagged> for $ty<T, $($h)?>
        where
            T: $crate::BitEncode<Ctx> $(+ $tbound0 $(+ $tbound1)?)?
        {
            fn encode<W, E>(&self,
                write: &mut W,
                ctx: &mut Ctx,
                _: $crate::Untagged,
            ) -> $crate::Result<()>
            where
                W: ::bitstream_io::BitWrite,
                E: ::bitstream_io::Endianness,
            {
                $crate::util::encode_items::<_, E, _, _>(self.iter(), write,  ctx)
            }
        }

        #[cfg(feature = "prepend-tags")]
        impl<Ctx, T, $($h)?> $crate::BitEncode<Ctx> for $ty<T, $($h)?>
        where
            T: $crate::BitEncode<Ctx> $(+ $tbound0 $(+ $tbound1)?)?
        {
            fn encode<W, E>(&self,
                write: &mut W,
                ctx: &mut Ctx,
                (): (),
            ) -> $crate::Result<()>
            where
                W: ::bitstream_io::BitWrite,
                E: ::bitstream_io::Endianness,
            {
                $crate::BitEncode::encode::<_, E>(&self.len(), write, ctx, ())?;
                $crate::BitEncode::encode::<_, E>(self, write, ctx, $crate::Untagged)
            }
        }
    }
}

#[cfg(feature = "alloc")]
mod vec {
    use alloc::vec::Vec;

    impl_read_list!(Vec<T>, |n| Self::with_capacity(n), push);
    impl_write_list!(Vec<T>);

    #[cfg(test)]
    mod tests {
        use crate::{Tag, Untagged};

        use super::*;

        test_untagged_and_codec!(
            Vec<u8>| Untagged, Tag(3); alloc::vec![1, 2, 3] => [0x01, 0x02, 0x03]
        );

        #[cfg(feature = "prepend-tags")]
        test_roundtrip!(Vec::<i32>);
    }
}

#[cfg(feature = "alloc")]
mod linked_list {
    use alloc::collections::linked_list::LinkedList;

    impl_read_list!(LinkedList<T>, |_| Self::new(), push_back);
    impl_write_list!(LinkedList<T>);

    #[cfg(test)]
    mod tests {
        use crate::{Tag, Untagged};

        use super::*;

        test_untagged_and_codec!(
            LinkedList<u8>| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03]
        );

        #[cfg(feature = "prepend-tags")]
        test_roundtrip!(LinkedList::<i32>);
    }
}

#[cfg(feature = "alloc")]
mod vec_deque {
    use alloc::collections::vec_deque::VecDeque;

    impl_read_list!(VecDeque<T>, |n| Self::with_capacity(n), push_back);
    impl_write_list!(VecDeque<T>);

    #[cfg(test)]
    mod tests {
        use crate::{Tag, Untagged};

        use super::*;

        test_untagged_and_codec!(
            VecDeque<u8>| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03]
        );

        #[cfg(feature = "prepend-tags")]
        test_roundtrip!(VecDeque::<i32>);
    }
}

#[cfg(feature = "alloc")]
mod b_tree_set {
    use alloc::collections::btree_set::BTreeSet;

    impl_read_list!(BTreeSet<T: Ord>, |_| Self::new(), insert);
    impl_write_list!(BTreeSet<T: Ord>);

    #[cfg(test)]
    mod tests {
        use crate::{Tag, Untagged};

        use super::*;

        test_untagged_and_codec!(
            BTreeSet<u8>| Untagged, Tag(3); [1, 2, 3].into() => [0x01, 0x02, 0x03]
        );

        #[cfg(feature = "prepend-tags")]
        test_roundtrip!(BTreeSet::<i32>);
    }
}

#[cfg(feature = "alloc")]
mod binary_heap {
    use alloc::collections::binary_heap::BinaryHeap;

    impl_read_list!(BinaryHeap<T: Ord>, |n| Self::with_capacity(n), push);
    impl_write_list!(BinaryHeap<T: Ord>);

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

        use bitstream_io::{BigEndian, BitReader};

        use crate::{BitDecode, Tag, Untagged};

        use super::*;

        #[test]
        fn decode() {
            let bytes: &[u8] = &[0x01];
            let exp: BinaryHeap<u8> = [1].into();
            let read: BinaryHeap<u8> = BitDecode::decode::<_, BigEndian>(
                &mut BitReader::endian(bytes, BigEndian),
                &mut (),
                Tag(1),
            )
            .unwrap();
            assert_eq!(
                exp.into_iter().collect::<Vec<_>>(),
                read.into_iter().collect::<Vec<_>>()
            );
        }

        test_encode!(BinaryHeap<u8>| Untagged; [1].into() => [0x01]);
    }
}

#[cfg(feature = "std")]
mod hash_set {
    use core::hash::{BuildHasher, Hash};
    use std::collections::HashSet;

    impl_read_list!(HashSet<T: Hash + Eq, H: BuildHasher + Default>, |n| Self::with_capacity_and_hasher(n, H::default()), insert);
    impl_write_list!(HashSet<T: Hash + Eq, H>);

    #[cfg(test)]
    mod tests {
        use crate::{Tag, Untagged};

        use super::*;

        test_untagged_and_codec!(HashSet<u8>| Untagged, Tag(1); [1].into() => [0x01]);

        #[cfg(feature = "prepend-tags")]
        test_roundtrip!(HashSet::<i32>);
    }
}
