use futures::{Stream, StreamExt};

use super::GraphView;
use crate::terms::Term;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnionView<G>(pub G);

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct UnionViewStream<G> {
    streams: G,
    finished: usize,
}

macro_rules! impl_union_view {
    ($(($tparams:ident, $idx:tt)),*) => {
        impl<$($tparams,)+> GraphView for UnionView<($($tparams,)+)>
        where
            $($tparams: GraphView,)+
        {
            type Stream = UnionViewStream<($($tparams::Stream,)+)>;
            async fn stream(self) -> Result<Self::Stream, super::GraphError> {
                Ok(UnionViewStream {
                    streams: ($(self.0 .$idx.stream().await?,)+),
                    finished: 0,
                })
            }
            type Filter<FilterType> = UnionView<($($tparams::Filter<FilterType>,)+)>
            where FilterType: super::TripleFilter + Send + Sync + Unpin + Clone;

            async fn filter<F>(self, filter: F) -> Result<Self::Filter<F>, super::GraphError>
            where
                F: super::TripleFilter + Send + Sync + Unpin + Clone,
            {
                Ok(UnionView((
                    $(self.0 .$idx.filter(filter.clone()).await?,)+
                )))
            }

            type Matches<MatchesType> = UnionView<($($tparams::Matches<MatchesType>,)+)>
            where MatchesType: super::Matches + Unpin;

            async fn matches<MatchesType>(
                self,
                matches: MatchesType,
                limit: Option<usize>,
            ) -> Result<Self::Matches<MatchesType>, super::GraphError>
            where MatchesType: super::Matches + Unpin
            {
                Ok(UnionView((
                    $(self.0 .$idx.matches(matches.clone(), limit).await?,)+
                )))
            }

            type CBD<T> = UnionView<($($tparams::CBD<T>,)+)> 
            where T: AsRef<Term> + Clone + Send;

            async fn cbd<T>(self, subject: T) -> Result<Self::CBD<T>, super::GraphError> 
            where T: AsRef<Term> + Clone + Send {
                Ok(UnionView((
                    $(self.0 .$idx.cbd(subject.clone()).await?,)+
                )))
            }

            type SubjectStream<'g> = UnionViewStream<($($tparams::SubjectStream<'g>),+)>;
            async fn subjects<'g>(
                self,
                predicate: Option<&'g crate::prelude::Term>,
                object: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::SubjectStream<'g>, super::GraphError> {
                Ok(UnionViewStream {
                    streams: (
                        $(self.0 .$idx.subjects(predicate, object, limit ).await?,)+
                    ),
                    finished: 0,
                })
            }

            type PredicateStream<'g> = UnionViewStream<($($tparams::PredicateStream<'g>),+)>;
            async fn predicates<'g>(
                self,
                subject: Option<&'g crate::prelude::Term>,
                object: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::PredicateStream<'g>, super::GraphError> {
                Ok(UnionViewStream {
                    streams: (
                        $(self.0 .$idx.predicates(subject, object, limit ).await?,)+
                    ),
                    finished: 0,
                })
            }

            type ObjectStream<'g> = UnionViewStream<($($tparams::ObjectStream<'g>),+)>;
            async fn objects<'g>(
                self,
                subject: Option<&'g crate::prelude::Term>,
                predicate: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::ObjectStream<'g>, super::GraphError> {
                Ok(UnionViewStream {
                    streams: (
                        $(self.0 .$idx.objects(subject, predicate, limit ).await?,)+
                    ),
                    finished: 0,
                })
            }

            async fn subject(
                self,
                predicate: Option<&crate::prelude::Term>,
                object: Option<&crate::prelude::Term>,
            ) -> Result<Option<std::sync::Arc<crate::prelude::Term>>, super::GraphError> {
                let subjects = ($(self.0 .$idx.subject(predicate, object).await?,)+);
                impl_union_view!(single_extractor subjects, $($idx,)+)
            }

            async fn predicate(
                self,
                subject: Option<&crate::prelude::Term>,
                object: Option<&crate::prelude::Term>,
            ) -> Result<Option<std::sync::Arc<crate::prelude::Term>>, super::GraphError> {
                let predicates = (
                    $(self.0 .$idx.predicate(subject, object).await?,)+
                );
                impl_union_view!(single_extractor predicates, $($idx,)+)
            }

            async fn object(
                self,
                subject: Option<&crate::prelude::Term>,
                predicate: Option<&crate::prelude::Term>,
            ) -> Result<Option<std::sync::Arc<crate::prelude::Term>>, super::GraphError> {
                let objects = (
                    $(self.0 .$idx.object(subject, predicate).await?,)+
                );
                impl_union_view!(single_extractor objects, $($idx,)+)
            }
            async fn contains(
                self,
                triple: crate::prelude::TripleRef<'_>,
            ) -> Result<bool, super::GraphError> {
                Ok(false $(|| self.0 .$idx.contains(triple).await?)+)
            }

            async fn ground(self) -> Result<bool, super::GraphError> {
                Ok(true $(&& self.0 .$idx.ground().await?)+)
            }

            $crate::impl_default_streams_dedup!();

            fn default_triple_size_hint() -> usize {
                [$($tparams::default_triple_size_hint(),)+].into_iter().max().expect("type list should be non-empty")
            }

            fn default_buf_size_hint() -> usize {
                [$($tparams::default_buf_size_hint(),)+].into_iter().max().expect("type list should be non-empty")
            }

            fn triple_size_hint(&self) -> usize {
                [$(self.0 .$idx.triple_size_hint(),)+].into_iter().max().expect("type list should be non-empty")
            }

            fn buf_size_hint(&self) -> usize {
                [$(self.0 .$idx.buf_size_hint(),)+].into_iter().max().expect("type list should be non-empty")
            }
        }

        impl<StreamType, $($tparams,)+> Stream for UnionViewStream<($($tparams,)+)>
        where
            $($tparams: Stream<Item = StreamType> + Unpin),+
        {
            type Item = StreamType;
            fn poll_next(
                mut self: std::pin::Pin<&mut Self>,
                cx: &mut std::task::Context<'_>,
            ) -> std::task::Poll<Option<Self::Item>> {
                let s = &mut *self;

                $(
                    if s.finished == $idx {
                        match s.streams.$idx.poll_next_unpin(cx) {
                            core::task::Poll::Ready(None) => s.finished += 1,
                            ret => return ret,
                        }
                    }

                )+
                if s.finished == impl_union_view!(count_idents $($tparams),+) {
                    return core::task::Poll::Ready(None)
                }
                unreachable!("we existed earlier")
            }
        }
    };

    (count_idents) => { 0 };
    (count_idents $head:ident $(,$tail:ident)*) => { 1 + impl_union_view!(count_idents $($tail),*) };
    (single_extractor $tuple:ident, $($idx:tt,)*) => {{
        let mut the_some = None;
        $(
            #[allow(unused_assignments)] // last found assignment superflous
            if let val @ Some(_) = $tuple.$idx {
                if the_some.is_some() {
                    return Err(super::GraphError::Ambiguous);
                } else {
                    the_some = val
                }
            }
        )+
        Ok(the_some)
    }};
}

impl_union_view!((G1, 0), (G2, 1));
impl_union_view!((G1, 0), (G2, 1), (G3, 2));
impl_union_view!((G1, 0), (G2, 1), (G3, 2), (G4, 3));
impl_union_view!((G1, 0), (G2, 1), (G3, 2), (G4, 3), (G5, 4));
impl_union_view!((G1, 0), (G2, 1), (G3, 2), (G4, 3), (G5, 4), (G6, 5));
impl_union_view!(
    (G1, 0),
    (G2, 1),
    (G3, 2),
    (G4, 3),
    (G5, 4),
    (G6, 5),
    (G7, 6)
);
impl_union_view!(
    (G1, 0),
    (G2, 1),
    (G3, 2),
    (G4, 3),
    (G5, 4),
    (G6, 5),
    (G7, 6),
    (G8, 7)
);
impl_union_view!(
    (G1, 0),
    (G2, 1),
    (G3, 2),
    (G4, 3),
    (G5, 4),
    (G6, 5),
    (G7, 6),
    (G8, 7),
    (G9, 8)
);
impl_union_view!(
    (G1, 0),
    (G2, 1),
    (G3, 2),
    (G4, 3),
    (G5, 4),
    (G6, 5),
    (G7, 6),
    (G8, 7),
    (G9, 8),
    (G10, 9)
);
