use crate::{
    graphs::SubjectMatch,
    prelude::*,
    quads::{filter::QuadFilter, Quad, QuadRef},
};

use futures::{Stream, StreamExt, TryStreamExt};
use thiserror::Error;

mod matches;
pub use matches::{FinalMatches, JoinedMatches, MatchesTuple, QuadMatches};
pub mod default;

#[derive(Debug, Error)]
pub enum DatasetError {
    #[error("Requested named graph does not exist")]
    UnknownNamedGraph,
    #[error("The named graph already exists in the dataset")]
    ConflictingNamedGraph,
    #[error("The dataset configuration contained errors: {0}")]
    DatasetConfigError(String),

    #[error("an underlying graph has failed")]
    GraphError(
        #[source]
        #[from]
        GraphError,
    ),

    #[error("Ambigious results while expecting a single term")]
    Ambigious,
}

macro_rules! single_term_search {
    ($name:ident with [ $arg1:ident, $arg2:ident, $arg3:ident ] on $self:ident) => {
        async move {
            let terms = $self
                .$name($arg1, $arg2, $arg3, Some(2))
                .await?
                .try_collect::<Vec<_>>()
                .await?;

            match terms.len() {
                0 => Ok(None),
                1 => Ok(Some(terms[0].clone())),
                2 => Err(DatasetError::Ambigious),
                _ => unreachable!("how did we get here? Is limit on matches working?"),
            }
        }
    };
}

pub trait DatasetView: Send + Sized + Clone {
    type Stream: Stream<Item = Result<Quad, DatasetError>> + Unpin + Send;

    fn stream(
        self,
    ) -> impl core::future::Future<Output = Result<Self::Stream, DatasetError>> + Send;

    type AssociatedGraphView: GraphView;

    fn to_graph_view(self) -> impl core::future::Future<Output = Self::AssociatedGraphView> + Send;

    type Filter<F>: DatasetView
    where
        F: QuadFilter + Unpin;

    fn filter<F>(
        self,
        filter: F,
    ) -> impl core::future::Future<Output = Result<Self::Filter<F>, DatasetError>> + Send
    where
        F: QuadFilter + Unpin;

    type Matches<M>: DatasetView
    where
        M: QuadMatches + Unpin;

    fn matches<M>(
        self,
        matches: M,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::Matches<M>, DatasetError>> + Send
    where
        M: QuadMatches + Unpin;

    fn describe(
        self,
        subject: &Term,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::Matches<SubjectMatch<'_>>, DatasetError>> + Send
    {
        self.matches(SubjectMatch::new(subject), limit)
    }

    type SubjectStream<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn subjects<'a>(
        self,
        predicate: Option<&'a Term>,
        object: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::SubjectStream<'a>, DatasetError>> + Send;

    type SubjectStreamDedup<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn subjects_dedup<'a>(
        self,
        predicate: Option<&'a Term>,
        object: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::SubjectStreamDedup<'a>, DatasetError>> + Send;

    type PredicateStream<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn predicates<'a>(
        self,
        subject: Option<&'a Term>,
        object: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::PredicateStream<'a>, DatasetError>> + Send;

    type PredicateStreamDedup<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn predicates_dedup<'a>(
        self,
        subject: Option<&'a Term>,
        object: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::PredicateStreamDedup<'a>, DatasetError>> + Send;

    type ObjectStream<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn objects<'a>(
        self,
        subject: Option<&'a Term>,
        predicate: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::ObjectStream<'a>, DatasetError>> + Send;

    type ObjectStreamDedup<'a>: Stream<Item = Result<Arc<Term>, DatasetError>> + Send + Unpin;

    fn objects_dedup<'a>(
        self,
        subject: Option<&'a Term>,
        predicate: Option<&'a Term>,
        graph_name: Option<Option<&'a Term>>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::ObjectStreamDedup<'a>, DatasetError>> + Send;

    type GraphNameStream<'a>: Stream<Item = Result<Option<Arc<Term>>, DatasetError>> + Send + Unpin;

    fn graph_names<'a>(
        self,
        subject: Option<&'a Term>,
        predicate: Option<&'a Term>,
        object: Option<&'a Term>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::GraphNameStream<'a>, DatasetError>> + Send;

    type GraphNameStreamDedup<'a>: Stream<Item = Result<Option<Arc<Term>>, DatasetError>>
        + Send
        + Unpin;

    fn graph_names_dedup<'a>(
        self,
        subject: Option<&'a Term>,
        predicate: Option<&'a Term>,
        object: Option<&'a Term>,
        limit: Option<usize>,
    ) -> impl core::future::Future<Output = Result<Self::GraphNameStreamDedup<'a>, DatasetError>> + Send;

    fn subject(
        self,
        predicate: Option<&Term>,
        object: Option<&Term>,
        quad_term: Option<Option<&Term>>,
    ) -> impl core::future::Future<Output = Result<Option<Arc<Term>>, DatasetError>> + Send {
        single_term_search!(subjects with [predicate, object, quad_term] on self)
    }

    fn predicate(
        self,
        subject: Option<&Term>,
        object: Option<&Term>,
        quad_term: Option<Option<&Term>>,
    ) -> impl core::future::Future<Output = Result<Option<Arc<Term>>, DatasetError>> + Send {
        single_term_search!(predicates with [subject, object, quad_term] on self)
    }

    fn object(
        self,
        subject: Option<&Term>,
        predicate: Option<&Term>,
        quad_term: Option<Option<&Term>>,
    ) -> impl core::future::Future<Output = Result<Option<Arc<Term>>, DatasetError>> + Send {
        single_term_search!(objects with [subject, predicate, quad_term] on self)
    }

    fn graph_name(
        self,
        subject: Option<&Term>,
        predicate: Option<&Term>,
        object: Option<&Term>,
    ) -> impl core::future::Future<Output = Result<Option<Option<Arc<Term>>>, DatasetError>> + Send
    {
        single_term_search!(graph_names with [subject, predicate, object] on self)
    }

    fn contains(
        self,
        quad: QuadRef<'_>,
    ) -> impl core::future::Future<Output = Result<bool, DatasetError>> + Send {
        async move {
            let matches = (
                Some(quad.subject()),
                Some(quad.predicate()),
                Some(quad.object()),
                Some(quad.graph_name()),
            );

            let quads = self
                .matches(matches, Some(1))
                .await?
                .stream()
                .await?
                .count()
                .await;
            Ok(quads > 0)
        }
    }
}

pub trait Dataset: Send + Graph {
    type View<'a>: DatasetView
    where
        Self: 'a;

    type Graph: Graph;

    type DefaultGraph: Graph;

    type DefaultView<'a>: GraphView
    where
        Self: 'a;

    fn view(&self) -> impl core::future::Future<Output = <Self as Dataset>::View<'_>> + Send;

    fn default_view(&self) -> impl core::future::Future<Output = Self::DefaultView<'_>> + Send;

    fn named_view<'a>(
        &'a self,
        name: &Term,
    ) -> impl core::future::Future<Output = Option<<Self::Graph as Graph>::View<'a>>> + Send;

    fn default_graph(&self) -> impl core::future::Future<Output = &Self::DefaultGraph> + Send;

    fn default_graph_mut(
        &mut self,
    ) -> impl core::future::Future<Output = Option<&mut Self::DefaultGraph>> + Send;

    fn named_graph(
        &self,
        name: &Term,
    ) -> impl core::future::Future<Output = Option<&Self::Graph>> + Send;

    fn named_graph_mut(
        &mut self,
        name: &Term,
    ) -> impl core::future::Future<Output = Option<&mut Self::Graph>> + Send;

    fn add_named_graph(
        &mut self,
        name: Arc<Term>,
        graph: Self::Graph,
    ) -> impl core::future::Future<Output = Result<(), DatasetError>> + Send;

    fn remove_named_graph(
        &mut self,
        name: &Term,
    ) -> impl core::future::Future<Output = Result<Option<Self::Graph>, DatasetError>>;
}

impl DatasetView for () {
    type Stream = futures::stream::Empty<Result<Quad, DatasetError>>;

    async fn stream(self) -> Result<Self::Stream, DatasetError> {
        Ok(futures::stream::empty())
    }

    type Filter<F>
        = ()
    where
        F: QuadFilter + Unpin;

    type AssociatedGraphView = ();
    async fn to_graph_view(self) -> Self::AssociatedGraphView {}

    async fn filter<F>(self, _filter: F) -> Result<Self::Filter<F>, DatasetError>
    where
        F: QuadFilter + Unpin,
    {
        Ok(())
    }

    type Matches<M>
        = ()
    where
        M: QuadMatches + Unpin;

    async fn matches<M>(
        self,
        _matches: M,
        _limit: Option<usize>,
    ) -> Result<Self::Matches<M>, DatasetError>
    where
        M: QuadMatches + Unpin,
    {
        Ok(())
    }

    type SubjectStream<'a> = futures::stream::Empty<Result<Arc<Term>, DatasetError>>;

    async fn subjects<'a>(
        self,
        _predicate: Option<&'a Term>,
        _object: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::SubjectStream<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type SubjectStreamDedup<'a> = Self::SubjectStream<'a>;

    async fn subjects_dedup<'a>(
        self,
        _predicate: Option<&'a Term>,
        _object: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::SubjectStreamDedup<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type PredicateStream<'a> = futures::stream::Empty<Result<Arc<Term>, DatasetError>>;

    async fn predicates<'a>(
        self,
        _subject: Option<&Term>,
        _object: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::PredicateStream<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }
    type PredicateStreamDedup<'a> = Self::PredicateStream<'a>;

    async fn predicates_dedup<'a>(
        self,
        _subject: Option<&'a Term>,
        _object: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::PredicateStreamDedup<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type ObjectStream<'a> = futures::stream::Empty<Result<Arc<Term>, DatasetError>>;

    async fn objects<'a>(
        self,
        _subject: Option<&Term>,
        _predicate: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::ObjectStream<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type ObjectStreamDedup<'a> = Self::ObjectStream<'a>;

    async fn objects_dedup<'a>(
        self,
        _subject: Option<&'a Term>,
        _predicate: Option<&'a Term>,
        _graph_name: Option<Option<&'a Term>>,
        _limit: Option<usize>,
    ) -> Result<Self::ObjectStreamDedup<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type GraphNameStream<'a> = futures::stream::Empty<Result<Option<Arc<Term>>, DatasetError>>;

    async fn graph_names<'a>(
        self,
        _subject: Option<&Term>,
        _predicate: Option<&'a Term>,
        _object: Option<&'a Term>,
        _limit: Option<usize>,
    ) -> Result<Self::GraphNameStream<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    type GraphNameStreamDedup<'a> = Self::GraphNameStream<'a>;

    async fn graph_names_dedup<'a>(
        self,
        _subject: Option<&'a Term>,
        _predicate: Option<&'a Term>,
        _object: Option<&'a Term>,
        _limit: Option<usize>,
    ) -> Result<Self::GraphNameStreamDedup<'a>, DatasetError> {
        Ok(futures::stream::empty())
    }

    async fn subject(
        self,
        _predicate: Option<&Term>,
        _object: Option<&Term>,
        _graph_name: Option<Option<&Term>>,
    ) -> Result<Option<Arc<Term>>, DatasetError> {
        Ok(None)
    }

    async fn predicate(
        self,
        _subject: Option<&Term>,
        _object: Option<&Term>,
        _graph_name: Option<Option<&Term>>,
    ) -> Result<Option<Arc<Term>>, DatasetError> {
        Ok(None)
    }

    async fn object(
        self,
        _subject: Option<&Term>,
        _predicate: Option<&Term>,
        _graph_name: Option<Option<&Term>>,
    ) -> Result<Option<Arc<Term>>, DatasetError> {
        Ok(None)
    }
    async fn graph_name(
        self,
        _subject: Option<&Term>,
        _predicate: Option<&Term>,
        _object: Option<&Term>,
    ) -> Result<Option<Option<Arc<Term>>>, DatasetError> {
        Ok(None)
    }

    async fn contains(self, _quad: QuadRef<'_>) -> Result<bool, DatasetError> {
        Ok(false)
    }
}
