//! Implementations for variously lengthed tuples.

use crate::de::{Decode, DecodePacked, Decoder, SequenceDecoder};
use crate::en::{Encode, EncodePacked, Encoder, SequenceEncoder};
use crate::Allocator;

macro_rules! count {
    (_) => { 1 };
    (_ _) => { 2 };
    (_ _ _) => { 3 };
    (_ _ _ _) => { 4 };
    (_ _ _ _ _) => { 5 };
    (_ _ _ _ _ _) => { 6 };
    (_ _ _ _ _ _ _) => { 7 };
    (_ _ _ _ _ _ _ _) => { 8 };
    (_ _ _ _ _ _ _ _ _) => { 9 };
    (_ _ _ _ _ _ _ _ _ _) => { 10 };
    (_ _ _ _ _ _ _ _ _ _ _) => { 11 };
    (_ _ _ _ _ _ _ _ _ _ _ _) => { 12 };
    (_ _ _ _ _ _ _ _ _ _ _ _ _) => { 13 };
    (_ _ _ _ _ _ _ _ _ _ _ _ _ _) => { 14 };
    (_ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => { 15 };
    (_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _) => { 16 };

    (( $($s:tt)* ) $_:ident $($tail:tt)*) => {
        count!(( $($s)* _ ) $($tail)*)
    };

    (( $($s:tt)* )) => {
        count!( $($s)* )
    };

    ($($ident:ident)*) => {
        count!(() $($ident)*)
    };
}

macro_rules! declare {
    () => {
    };

    (($ty0:ident, $ident0:ident) $(, ($ty:ident, $ident:ident))* $(,)?) => {
        impl<M, $ty0 $(, $ty)*> Encode<M> for ($ty0, $($ty),*)
        where
            $ty0: Encode<M>,
            $($ty: Encode<M>),*
        {
            // It is harder to check that a tuple is packed, because we have to
            // ensure it doesn't contain any padding.
            const IS_BITWISE_ENCODE: bool = false;

            #[inline]
            fn encode<E>(&self, encoder: E) -> Result<(), E::Error>
            where
                E: Encoder<Mode = M>,
            {
                encoder.encode_sequence_fn(count!($ident0 $($ident)*), |tuple| {
                    let ($ident0, $($ident),*) = self;
                    tuple.encode_next()?.encode($ident0)?;
                    $(tuple.encode_next()?.encode($ident)?;)*
                    Ok(())
                })
            }

            type Encode = Self;

            #[inline]
            fn as_encode(&self) -> &Self::Encode {
                self
            }
        }

        impl<'de, M, A, $ty0, $($ty,)*> Decode<'de, M, A> for ($ty0, $($ty),*)
        where
            A: Allocator,
            $ty0: Decode<'de, M, A>,
            $($ty: Decode<'de, M, A>),*
        {
            // It is harder to check that a tuple is packed, because we have to
            // ensure it doesn't contain any padding.
            const IS_BITWISE_DECODE: bool = false;

            #[inline]
            fn decode<D>(decoder: D) -> Result<Self, D::Error>
            where
                D: Decoder<'de, Mode = M, Allocator = A>,
            {
                decoder.decode_sequence_hint(count!($ident0 $($ident)*), |tuple| {
                    let $ident0 = tuple.next()?;
                    $(let $ident = tuple.next()?;)*
                    Ok(($ident0, $($ident),*))
                })
            }
        }

        impl<M, $ty0 $(,$ty)*> EncodePacked<M> for ($ty0, $($ty),*)
        where
            $ty0: Encode<M>,
            $($ty: Encode<M>),*
        {
            #[inline]
            fn encode_packed<E>(&self, encoder: E) -> Result<(), E::Error>
            where
                E: Encoder<Mode = M>,
            {
                let ($ident0, $($ident),*) = self;
                encoder.encode_pack_fn(|pack| {
                    pack.encode_next()?.encode($ident0)?;
                    $(pack.encode_next()?.encode($ident)?;)*
                    Ok(())
                })
            }
        }

        impl<'de, M, A, $ty0, $($ty,)*> DecodePacked<'de, M, A> for ($ty0, $($ty),*)
        where
            A: Allocator,
            $ty0: Decode<'de, M, A>,
            $($ty: Decode<'de, M, A>),*
        {
            #[inline]
            fn decode_packed<D>(decoder: D) -> Result<Self, D::Error>
            where
                D: Decoder<'de, Mode = M, Allocator = A>,
            {
                decoder.decode_pack(|pack| {
                    let $ident0 = pack.next()?;
                    $(let $ident = pack.next()?;)*
                    Ok(($ident0, $($ident),*))
                })
            }
        }

        declare!($(($ty, $ident)),*);
    };
}

declare! {
    (T0, t0),
    (T1, t1),
    (T2, t2),
    (T3, t3),
    (T4, t4),
    (T5, t5),
    (T6, t6),
    (T7, t7),
    (T8, t8),
    (T9, t9),
    (T10, t10),
    (T11, t11),
    (T12, t12),
    (T13, t13),
    (T14, t14),
    (T15, t15),
}
