use core::pin::Pin;
use std::sync::Arc;

use futures::{Stream, StreamExt};

use crate::terms::Term;
use crate::triples::Triple;

use super::{Graph, GraphError, GraphView, TripleFilter};

macro_rules! impl_either {
    ($name:ident => $(($tparams:ident, $variants:ident)),+) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub enum $name<$($tparams,)+> {
            $($variants($tparams),)+
        }

        impl<$($tparams,)+> $crate::graphs::Graph for $name<$($tparams,)+>
        where
            $($tparams: Graph + Sync, )+
        {
            type View<'either_graph> = $name<$($tparams::View<'either_graph>,)+>
            where
                Self: 'either_graph;

            async fn view(&self) -> Self::View<'_> {
                match self {
                    $($name::$variants(g) => $name::$variants(g.view().await),)+
                }
            }

            async fn add(&mut self, triple: Arc<Triple>) -> Result<(), GraphError> {
                match self {
                    $($name::$variants(g) => g.add(triple).await,)+
                }
            }
            async fn remove(&mut self, triple: $crate::prelude::TripleRef<'_>) -> Result<(), GraphError> {
                match self {
                    $($name::$variants(g) => g.remove(triple).await,)+
                }
            }
            async fn merge_stream<S, E>(&mut self, stream: S) -> Result<(), GraphError>
            where
                S: futures::Stream<Item = Result<Arc<Triple>, E>> + Unpin + Send,
                GraphError: From<E>,
                E: Send,
            {
                match self {
                    $($name::$variants(g) => g.merge_stream(stream).await,)+
                }
            }
            async fn merge<G>(&mut self, source_graph: G) -> Result<(), GraphError>
            where
                G: GraphView,
            {
                match self {
                    $($name::$variants(g) => g.merge(source_graph).await,)+
                }
            }
        }
        impl<$($tparams,)+> $crate::graphs::GraphView for $name<$($tparams,)+>
        where
            $($tparams: GraphView,)+
        {

            type Stream = $name<$($tparams::Stream,)+>;
            async fn stream(self) -> Result<Self::Stream, super::GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.stream().await?)),)+
                }
            }

            type Filter<TripleF>
                = $name<$($tparams::Filter<TripleF>,)+>
            where
                TripleF: TripleFilter + Send + Sync + Unpin + Clone;

            async fn filter<F>(self, filter: F) -> Result<Self::Filter<F>, GraphError>
            where
                F: TripleFilter + Send + Sync + Unpin + Clone,
            {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.filter(filter).await?)),)+
                }
            }

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

            async fn matches<MatchesType>(
                self,
                matches: MatchesType,
                limit: Option<usize>,
            ) -> Result<Self::Matches<MatchesType>, GraphError>
            where MatchesType: super::Matches + Unpin
            {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.matches(matches, limit).await?)),)+
                }
            }

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

            async fn cbd<T>(self, subject: T) -> Result<Self::CBD<T>, GraphError>
            where T: AsRef<Term> + Clone + Send
            {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.cbd(subject).await?)),)+
                }
            }

            type SubjectStream<'subject_query> = $name<$($tparams::SubjectStream<'subject_query>,)+>;

            async fn subjects<'g>(
                self,
                predicate: Option<&'g crate::prelude::Term>,
                object: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::SubjectStream<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.subjects(predicate, object, limit).await?)),)+
                }
            }

            type SubjectStreamDedup<'subject_query> = $name<$($tparams::SubjectStreamDedup<'subject_query>,)+>;

            async fn subjects_dedup<'g>(self, predicate: Option<&'g crate::prelude::Term>, object: Option<&'g crate::prelude::Term>, limit: Option<usize>) -> Result<Self::SubjectStreamDedup<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.subjects_dedup(predicate, object, limit).await?)),)+
                }
            }

            type PredicateStream<'predicate_query> = $name<$($tparams::PredicateStream<'predicate_query>,)+>;

            async fn predicates<'g>(
                self,
                subject: Option<&'g crate::prelude::Term>,
                object: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::PredicateStream<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.predicates(subject, object, limit).await?)),)+
                }
            }

            type PredicateStreamDedup<'subject_query> = $name<$($tparams::PredicateStreamDedup<'subject_query>,)+>;

            async fn predicates_dedup<'g>(self, subject: Option<&'g crate::prelude::Term>, object: Option<&'g crate::prelude::Term>, limit: Option<usize>) -> Result<Self::PredicateStreamDedup<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.predicates_dedup(subject, object, limit).await?)),)+
                }
            }
            type ObjectStream<'object_query> = $name<$($tparams::ObjectStream<'object_query>,)+>;

            async fn objects<'g>(
                self,
                subject: Option<&'g crate::prelude::Term>,
                predicate: Option<&'g crate::prelude::Term>,
                limit: Option<usize>,
            ) -> Result<Self::ObjectStream<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.objects(subject, predicate, limit).await?)),)+
                }
            }

            type ObjectStreamDedup<'subject_query> = $name<$($tparams::ObjectStreamDedup<'subject_query>,)+>;

            async fn objects_dedup<'g>(self, subject: Option<&'g crate::prelude::Term>, predicate: Option<&'g crate::prelude::Term>, limit: Option<usize>) -> Result<Self::ObjectStreamDedup<'g>, GraphError> {
                match self {
                    $($name::$variants(g) => Ok($name::$variants(g.objects_dedup(subject, predicate, limit).await?)),)+
                }
            }

            async fn subject(
                self,
                predicate: Option<&crate::prelude::Term>,
                object: Option<&crate::prelude::Term>,
            ) -> Result<Option<Arc<crate::prelude::Term>>, GraphError> {
                match self {
                    $($name::$variants(g) => g.subject(predicate, object).await,)+
                }
            }

            async fn predicate(
                self,
                subject: Option<&crate::prelude::Term>,
                object: Option<&crate::prelude::Term>,
            ) -> Result<Option<Arc<crate::prelude::Term>>, GraphError> {
                match self {
                    $($name::$variants(g) => g.predicate(subject, object).await,)+
                }
            }

            async fn object(
                self,
                subject: Option<&crate::prelude::Term>,
                predicate: Option<&crate::prelude::Term>,
            ) -> Result<Option<Arc<crate::prelude::Term>>, GraphError> {
                match self {
                    $($name::$variants(g) => g.object(subject, predicate).await,)+
                }
            }

            async fn contains(self, triple: crate::prelude::TripleRef<'_>) -> Result<bool, GraphError> {
                match self {
                    $($name::$variants(g) => g.contains(triple).await,)+
                }
            }

            async fn ground(self) -> Result<bool, GraphError> {
                match self {
                    $($name::$variants(g) => g.ground().await,)+
                }
            }

            fn default_triple_size_hint() -> usize {
                [$($tparams::default_triple_size_hint(),)+].into_iter().max().expect("ether graphs should have more than zero type params")
            }
            fn default_buf_size_hint() -> usize {
                [$($tparams::default_buf_size_hint(),)+].into_iter().max().expect("ether graphs should have more than zero type params")
            }
            fn triple_size_hint(&self) -> usize {
                match self {
                    $($name::$variants(g) => g.triple_size_hint(),)+
                }
            }

            fn buf_size_hint(&self) -> usize {
                match self {
                    $($name::$variants(g) => g.buf_size_hint(),)+
                }
            }
        }

        impl<StreamType, $($tparams,)+> futures::stream::Stream for $name<$($tparams,)+>
        where
            $($tparams: Stream<Item = StreamType> + Unpin,)+
        {
            type Item = StreamType;

            fn poll_next(
                mut self: Pin<&mut Self>,
                cx: &mut std::task::Context<'_>,
            ) -> std::task::Poll<Option<Self::Item>> {
                match &mut *self {
                    $($name::$variants(g) => g.poll_next_unpin(cx),)+
                }
            }
        }
    };
}

impl_either! { EitherGraph2 => (G1, N1), (G2, N2) }
impl_either! { EitherGraph3 => (G1, N1), (G2, N2), (G3, N3) }
impl_either! { EitherGraph4 => (G1, N1), (G2, N2), (G3, N3), (G4, N4) }
impl_either! { EitherGraph5 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5) }
impl_either! { EitherGraph6 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5), (G6, N6) }
impl_either! { EitherGraph7 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5), (G6, N6), (G7, N7) }
impl_either! { EitherGraph8 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5), (G6, N6), (G7, N7), (G8, N8) }
impl_either! { EitherGraph9 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5), (G6, N6), (G7, N7), (G8, N8), (G9, N9) }
impl_either! { EitherGraph10 => (G1, N1), (G2, N2), (G3, N3), (G4, N4), (G5, N5), (G6, N6), (G7, N7), (G8, N8), (G9, N9), (G10, N10) }
