use std::collections::*;
use std::fmt;
use std::hash::{BuildHasher, Hash};
use std::marker::PhantomData;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::num::{NonZeroI128, NonZeroU128};
use std::sync::Arc;
use std::time::Duration;

use bytes::Bytes;
use futures::stream::Stream;
use uuid::Uuid;

use super::{EncodeTuple, Encoder, IntoStream, MapStream, SeqStream, ToStream};

macro_rules! autoencode {
    ($ty:ident, $method:ident $($cast:tt)*) => {
        impl<'en> ToStream<'en> for $ty {
            fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.$method(*self $($cast)*)
            }
        }

        impl<'en> IntoStream<'en> for $ty {
            fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.$method(self $($cast)*)
            }
        }
    }
}

autoencode!(bool, encode_bool);
autoencode!(isize, encode_i64 as i64);
autoencode!(i8, encode_i8);
autoencode!(i16, encode_i16);
autoencode!(i32, encode_i32);
autoencode!(i64, encode_i64);
autoencode!(usize, encode_u64 as u64);
autoencode!(u8, encode_u8);
autoencode!(u16, encode_u16);
autoencode!(u32, encode_u32);
autoencode!(u64, encode_u64);
autoencode!(f32, encode_f32);
autoencode!(f64, encode_f64);

impl<'en> ToStream<'en> for i128 {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for i128 {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for u128 {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for u128 {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for NonZeroI128 {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.get().to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for NonZeroI128 {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.get().to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for NonZeroU128 {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.get().to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for NonZeroU128 {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.get().to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for IpAddr {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for IpAddr {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for Ipv4Addr {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for Ipv4Addr {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for Ipv6Addr {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for Ipv6Addr {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for SocketAddr {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> IntoStream<'en> for SocketAddr {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        IntoStream::into_stream(self.to_string(), encoder)
    }
}

impl<'en> ToStream<'en> for Duration {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        let mut tuple = encoder.encode_tuple(2)?;
        tuple.encode_element(self.as_secs())?;
        tuple.encode_element(self.subsec_nanos())?;
        tuple.end()
    }
}

impl<'en> IntoStream<'en> for Duration {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        let mut tuple = encoder.encode_tuple(2)?;
        tuple.encode_element(self.as_secs())?;
        tuple.encode_element(self.subsec_nanos())?;
        tuple.end()
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en> IntoStream<'en> for () {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_unit()
    }
}

impl<'en> ToStream<'en> for () {
    fn to_stream<E: Encoder<'en>>(&self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_unit()
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'a, 'en> IntoStream<'en> for &'a str
where
    'a: 'en,
{
    fn into_stream<E: Encoder<'en>>(
        self,
        encoder: E,
    ) -> Result<<E as Encoder<'en>>::Ok, <E as Encoder<'en>>::Error> {
        encoder.encode_str(self)
    }
}

impl<'a, 'en> ToStream<'en> for &'a str
where
    'a: 'en,
{
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_str(self)
    }
}

impl<'en> IntoStream<'en> for String {
    fn into_stream<E: Encoder<'en>>(
        self,
        encoder: E,
    ) -> Result<<E as Encoder<'en>>::Ok, <E as Encoder<'en>>::Error> {
        encoder.encode_str(&self)
    }
}

impl<'en> ToStream<'en> for String {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_str(self)
    }
}

impl<'a, 'en> IntoStream<'en> for fmt::Arguments<'a>
where
    'a: 'en,
{
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_str(&self)
    }
}

impl<'a, 'en> ToStream<'en> for fmt::Arguments<'a>
where
    'a: 'en,
{
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_str(self)
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en, T: IntoStream<'en> + 'en> IntoStream<'en> for Option<T> {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        match self {
            Some(value) => encoder.encode_some(value),
            None => encoder.encode_none(),
        }
    }
}

impl<'en, T: ToStream<'en> + 'en> ToStream<'en> for Option<T> {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        match *self {
            Some(ref value) => encoder.encode_some(value),
            None => encoder.encode_none(),
        }
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en, T: ?Sized> IntoStream<'en> for PhantomData<T> {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_unit()
    }
}

impl<'en, T: ?Sized> ToStream<'en> for PhantomData<T> {
    fn to_stream<E: Encoder<'en>>(&self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_unit()
    }
}

////////////////////////////////////////////////////////////////////////////////

// Does not require T: IntoStream.
impl<'en, T> IntoStream<'en> for [T; 0] {
    fn into_stream<E: Encoder<'en>>(
        self,
        encoder: E,
    ) -> Result<<E as Encoder<'en>>::Ok, <E as Encoder<'en>>::Error> {
        let seq = encoder.encode_tuple(0)?;
        seq.end()
    }
}

// Does not require T: ToStream.
impl<'en, T> ToStream<'en> for [T; 0] {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        let seq = encoder.encode_tuple(0)?;
        seq.end()
    }
}

macro_rules! encode_array {
    ($($len:tt)+) => {
        $(
            impl<'a, 'en, T: ToStream<'en> + 'en> IntoStream<'en> for &'a [T; $len] where 'a: 'en {
                fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                    let mut seq = encoder.encode_tuple($len)?;
                    for e in self {
                        seq.encode_element(e)?;
                    }
                    seq.end()
                }
            }

            impl<'a, 'en, T: ToStream<'en> + 'en> ToStream<'en> for &'a [T; $len] where 'a: 'en {
                fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
                    let mut seq = encoder.encode_tuple($len)?;
                    for e in *self {
                        seq.encode_element(e)?;
                    }
                    seq.end()
                }
            }
        )+
    }
}

encode_array! {
    1 2 3 4 5 6 7 8 9 10
    11 12 13 14 15 16 17 18 19 20
    21 22 23 24 25 26 27 28 29 30
    31 32
}

////////////////////////////////////////////////////////////////////////////////

impl<'a, 'en, T: ToStream<'en> + 'en> IntoStream<'en> for &'a [T]
where
    'a: 'en,
{
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_seq(self)
    }
}

impl<'a, 'en, T: ToStream<'en>> ToStream<'en> for &'a [T]
where
    'a: 'en,
{
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_seq(*self)
    }
}

macro_rules! encode_seq {
    ($ty:ident < T $(: $tbound1:ident $(+ $tbound2:ident)*)* $(, $typaram:ident : $bound:ident)* >) => {
        impl<'en, T $(, $typaram)*> IntoStream<'en> for $ty<T $(, $typaram)*>
        where
            T: IntoStream<'en> + 'en $(+ $tbound1 $(+ $tbound2)*)*,
            $($typaram: $bound,)*
        {
            fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.collect_seq(self)
            }
        }

        impl<'en, T $(, $typaram)*> ToStream<'en> for $ty<T $(, $typaram)*>
        where
            T: ToStream<'en> + 'en $(+ $tbound1 $(+ $tbound2)*)*,
            $($typaram: $bound,)*
        {
            fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.collect_seq(self)
            }
        }
    }
}

encode_seq!(BinaryHeap<T: Ord>);
encode_seq!(BTreeSet<T: Ord>);
encode_seq!(HashSet<T: Eq + Hash, H: BuildHasher>);
encode_seq!(LinkedList<T>);
encode_seq!(Vec<T>);
encode_seq!(VecDeque<T>);

#[cfg(feature = "smallvec")]
impl<'en, A: smallvec::Array> IntoStream<'en> for smallvec::SmallVec<A>
where
    A::Item: IntoStream<'en> + 'en,
{
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_seq(self)
    }
}

#[cfg(feature = "smallvec")]
impl<'en, A: smallvec::Array> ToStream<'en> for smallvec::SmallVec<A>
where
    A::Item: ToStream<'en> + 'en,
{
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_seq(self)
    }
}

////////////////////////////////////////////////////////////////////////////////

macro_rules! encode_tuple {
    ($($len:expr => ($($n:tt $name:ident)+))+) => {
        $(
            impl<'en, $($name),+> IntoStream<'en> for ($($name,)+)
            where
                $($name: IntoStream<'en> + 'en,)+
            {
                fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                    let mut tuple = encoder.encode_tuple($len)?;
                    $(
                        tuple.encode_element(self.$n)?;
                    )+
                    tuple.end()
                }
            }

            impl<'en, $($name),+> ToStream<'en> for ($($name,)+)
            where
                $($name: ToStream<'en> + 'en,)+
            {
                fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
                    let mut tuple = encoder.encode_tuple($len)?;
                    $(
                        tuple.encode_element(&self.$n)?;
                    )+
                    tuple.end()
                }
            }
        )+
    }
}

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

////////////////////////////////////////////////////////////////////////////////

macro_rules! encode_map {
    ($ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound:ident)* >) => {
        impl<'en, K, V $(, $typaram)*> IntoStream<'en> for $ty<K, V $(, $typaram)*>
        where
            K: IntoStream<'en> + 'en $(+ $kbound1 $(+ $kbound2)*)*,
            V: IntoStream<'en> + 'en,
            $($typaram: $bound,)*
        {
            fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.collect_map(self)
            }
        }

        impl<'en, K, V $(, $typaram)*> ToStream<'en> for $ty<K, V $(, $typaram)*>
        where
            K: ToStream<'en> + 'en $(+ $kbound1 $(+ $kbound2)*)*,
            V: ToStream<'en> + 'en,
            $($typaram: $bound,)*
        {
            fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
                encoder.collect_map(self)
            }
        }
    }
}

encode_map!(BTreeMap<K: Ord, V>);
encode_map!(HashMap<K: Eq + Hash, V, H: BuildHasher>);

////////////////////////////////////////////////////////////////////////////////

impl<'en, T: ToStream<'en> + 'en> ToStream<'en> for Arc<T> {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        (**self).to_stream(encoder)
    }
}

impl<'en, T: IntoStream<'en> + 'en> IntoStream<'en> for Box<T> {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        (*self).into_stream(encoder)
    }
}

impl<'en, T: ToStream<'en> + 'en> ToStream<'en> for Box<T> {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        (**self).to_stream(encoder)
    }
}

macro_rules! encode_ref {
    (
        $(#[doc = $doc:tt])*
        <$($desc:tt)+
    ) => {
        $(#[doc = $doc])*
        impl <$($desc)+ {
            fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
                (*self).to_stream(encoder)
            }
        }
    };
}

encode_ref!(<'a, 'en, T: ?Sized> IntoStream<'en> for &'a T where T: ToStream<'en> + 'en, 'a: 'en);
encode_ref!(<'a, 'en, T: ?Sized> IntoStream<'en> for &'a mut T where T: ToStream<'en> + 'en, 'a: 'en);

////////////////////////////////////////////////////////////////////////////////

impl<'en, K, V, S> IntoStream<'en> for MapStream<K, V, S>
where
    K: IntoStream<'en> + 'en,
    V: IntoStream<'en> + 'en,
    S: Stream<Item = (K, V)> + Send + Unpin + 'en,
{
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_map_stream(self.into_inner())
    }
}

impl<'en, T, S> IntoStream<'en> for SeqStream<T, S>
where
    T: IntoStream<'en> + 'en,
    S: Stream<Item = T> + Send + Unpin + 'en,
{
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_seq_stream(self.into_inner())
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en, T: IntoStream<'en>, Err: IntoStream<'en>> IntoStream<'en> for Result<T, Err> {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        match self {
            Self::Ok(value) => value.into_stream(encoder),
            Self::Err(error) => error.into_stream(encoder),
        }
    }
}

impl<'en, T: ToStream<'en>, Err: ToStream<'en>> ToStream<'en> for Result<T, Err> {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        match self {
            Self::Ok(value) => value.to_stream(encoder),
            Self::Err(error) => error.to_stream(encoder),
        }
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en> IntoStream<'en> for Bytes {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_bytes(self)
    }
}

impl<'en> ToStream<'en> for Bytes {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.collect_bytes(self.iter().copied())
    }
}

////////////////////////////////////////////////////////////////////////////////

impl<'en> IntoStream<'en> for Uuid {
    fn into_stream<E: Encoder<'en>>(self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_uuid(self)
    }
}

impl<'en> ToStream<'en> for Uuid {
    fn to_stream<E: Encoder<'en>>(&'en self, encoder: E) -> Result<E::Ok, E::Error> {
        encoder.encode_uuid(*self)
    }
}
