use futures::{future::Ready, stream, StreamExt};

use crate::{
    datasets::{DatasetView, QuadMatches}, graphs::{default::DefaultMatches, GraphView, MatchesFilter}, impl_dataset_subject_stream, impl_default_dataset_view, prelude::DatasetError, quads::Quad
};

fn second_tuple_entry<A, B>((_, t): (A, B)) -> B {
    t
}

#[macro_export]
macro_rules! impl_default_dataset_matches {
    () => {
        type Matches<MatchesType>
            = $crate::graphs::default::DefaultMatches<MatchesType, Self>
        where
            MatchesType: $crate::datasets::QuadMatches + ::core::marker::Unpin;

        async fn matches<MatchesType>(
            self,
            matches: MatchesType,
            limit: Option<usize>,
        ) -> Result<Self::Matches<MatchesType>, $crate::prelude::DatasetError>
        where
            MatchesType: $crate::datasets::QuadMatches + ::core::marker::Unpin,
        {
            Ok($crate::graphs::default::DefaultMatches::new(
                self, matches, limit,
            ))
        }
    };
}

impl<M, D> DatasetView for DefaultMatches<M, D>
where
    M: QuadMatches + Unpin,
    D: DatasetView,
{
    type Stream = stream::Map<
        stream::TakeWhile<
            stream::Enumerate<
                <<D as DatasetView>::Filter<MatchesFilter<M>> as DatasetView>::Stream,
            >,
            Ready<bool>,
            Box<dyn Send + Fn(&(usize, Result<Quad, DatasetError>)) -> Ready<bool>>,
        >,
        fn((usize, Result<Quad, DatasetError>)) -> Result<Quad, DatasetError>,
    >;


    fn stream(
        self,
    ) -> impl core::future::Future<Output = Result<Self::Stream, DatasetError>> + Send {
        let limit_future = Box::new(move |(i, _): &(usize, Result<Quad, DatasetError>)| {
            futures::future::ready(match self.limit {
                Some(0) => true,
                Some(n) => *i < n,
                None => true,
            })
        }) as Box<_>;

        async move {
            Ok(self
                .view
                .filter(self.filter)
                .await?
                .stream()
                .await?
                .enumerate()
                .take_while(limit_future)
                .map(second_tuple_entry as _))
        }
    }

    impl_default_dataset_view!();
}
