use std::{collections::HashSet, sync::Arc};

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

use crate::{
    default_graph_view, graphs::{GraphError, GraphView, Matches, MatchesFilter, TripleFilter}, terms::Term, triples::Triple
};

#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct DefaultMatches<M, G> {
    pub(crate) view: G,
    pub(crate) filter: MatchesFilter<M>,
    pub(crate) limit: Option<usize>,
}

impl<G, M> DefaultMatches<M, G> {
    pub fn new(view: G, matches: M, limit: Option<usize>) -> Self {
        Self {
            view,
            filter: MatchesFilter(matches),
            limit,
        }
    }
}

#[macro_export]
macro_rules! impl_default_matches {
    () => {
        type Matches<MatchesType>
            = $crate::graphs::default::DefaultMatches<MatchesType, Self>
        where
            MatchesType: $crate::graphs::Matches + ::core::marker::Unpin;
        async fn matches<MatchesType>(
            self,
            matches: MatchesType,
            limit: Option<usize>,
        ) -> Result<Self::Matches<MatchesType>, $crate::prelude::GraphError>
        where
            MatchesType: $crate::graphs::Matches + ::core::marker::Unpin,
        {
            Ok($crate::graphs::default::DefaultMatches::new(
                self, matches, limit,
            ))
        }
    };
}

impl<M, G> GraphView for DefaultMatches<M, G>
where
    G: GraphView,
    M: Matches + Unpin,
{
    type Stream = stream::Map<
        stream::TakeWhile<
            stream::Enumerate<<<G as GraphView>::Filter<MatchesFilter<M>> as GraphView>::Stream>,
            Ready<bool>,
            Box<dyn Send + Fn(&(usize, Result<Arc<Triple>, GraphError>)) -> Ready<bool>>,
        >,
        fn((usize, Result<Arc<Triple>, GraphError>)) -> Result<Arc<Triple>, GraphError>,
    >;

    fn stream(
        self,
    ) -> impl core::future::Future<Output = Result<Self::Stream, crate::prelude::GraphError>> + Send
    {
        let limit_future = Box::new(move |(i, _): &(usize, Result<Arc<Triple>, GraphError>)| {
            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 _))
        }
    }

    default_graph_view!();
}

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