use crate::{tags, Index, Result, Stream, Value};

impl<T: Value> Value for [T] {
    fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
        stream.seq_begin(Some(self.len()))?;

        for elem in self {
            stream.seq_value_begin()?;
            stream.value(elem)?;
            stream.seq_value_end()?;
        }

        stream.seq_end()
    }
}

impl<T: Value, const N: usize> Value for [T; N] {
    fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
        stream.tagged_begin(Some(&tags::CONSTANT_SIZE), None, None)?;
        stream.value(self as &'a [T])?;
        stream.tagged_end(Some(&tags::CONSTANT_SIZE), None, None)
    }
}

macro_rules! tuple {
    ($(
        $len:expr => ( $(self.$i:tt: $ty:ident,)+ ),
    )+) => {
        $(
            impl<$($ty: Value),+> Value for ($($ty,)+) {
                fn stream<'sval, S: Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> Result {
                    stream.tuple_begin(None, None, None, Some($len))?;

                    $(
                        stream.tuple_value_begin(None, &Index::new($i).with_tag(&tags::VALUE_OFFSET))?;
                        stream.value(&self.$i)?;
                        stream.tuple_value_end(None, &Index::new($i).with_tag(&tags::VALUE_OFFSET))?;
                    )+

                    stream.tuple_end(None, None, None)
                }
            }
        )+
    }
}

tuple! {
    1 => (
        self.0: T0,
    ),
    2 => (
        self.0: T0,
        self.1: T1,
    ),
    3 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
    ),
    4 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
    ),
    5 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
    ),
    6 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
    ),
    7 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
    ),
    8 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
    ),
    9 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
    ),
    10 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
    ),
    11 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
    ),
    12 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
        self.11: T11,
    ),
    13 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
        self.11: T11,
        self.12: T12,
    ),
    14 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
        self.11: T11,
        self.12: T12,
        self.13: T13,
    ),
    15 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
        self.11: T11,
        self.12: T12,
        self.13: T13,
        self.14: T14,
    ),
    16 => (
        self.0: T0,
        self.1: T1,
        self.2: T2,
        self.3: T3,
        self.4: T4,
        self.5: T5,
        self.6: T6,
        self.7: T7,
        self.8: T8,
        self.9: T9,
        self.10: T10,
        self.11: T11,
        self.12: T12,
        self.13: T13,
        self.14: T14,
        self.15: T15,
    ),
}

#[cfg(feature = "alloc")]
mod alloc_support {
    use super::*;

    use crate::std::{
        collections::{BTreeSet, BinaryHeap, LinkedList, VecDeque},
        vec::Vec,
    };

    impl<T: Value> Value for Vec<T> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            (&**self).stream(stream)
        }
    }

    impl<T: Value> Value for BTreeSet<T> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            stream.seq_begin(Some(self.len()))?;

            for elem in self {
                stream.seq_value_begin()?;
                stream.value(elem)?;
                stream.seq_value_end()?;
            }

            stream.seq_end()
        }
    }

    impl<T: Value> Value for VecDeque<T> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            stream.seq_begin(Some(self.len()))?;

            for elem in self {
                stream.seq_value_begin()?;
                stream.value(elem)?;
                stream.seq_value_end()?;
            }

            stream.seq_end()
        }
    }

    impl<T: Value> Value for LinkedList<T> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            stream.seq_begin(Some(self.len()))?;

            for elem in self {
                stream.seq_value_begin()?;
                stream.value(elem)?;
                stream.seq_value_end()?;
            }

            stream.seq_end()
        }
    }

    impl<T: Value> Value for BinaryHeap<T> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            stream.seq_begin(Some(self.len()))?;

            for elem in self {
                stream.seq_value_begin()?;
                stream.value(elem)?;
                stream.seq_value_end()?;
            }

            stream.seq_end()
        }
    }
}

#[cfg(feature = "std")]
mod std_support {
    use super::*;

    use crate::std::{collections::HashSet, hash::BuildHasher};

    impl<T: Value, H: BuildHasher> Value for HashSet<T, H> {
        fn stream<'a, S: Stream<'a> + ?Sized>(&'a self, stream: &mut S) -> Result {
            stream.seq_begin(Some(self.len()))?;

            for elem in self {
                stream.seq_value_begin()?;
                stream.value(elem)?;
                stream.seq_value_end()?;
            }

            stream.seq_end()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn array_tag() {
        assert_eq!(Some(tags::CONSTANT_SIZE), [true, false].tag());
    }
}
