use std::sync::Arc;

use futures::{Stream, StreamExt};

use crate::{terms::Term, triples::Triple};

use super::{GraphError, GraphView, JoinedMatches, Matches, TripleFilter};

pub trait Bijection {
    type Domain;
    type Image;

    fn f(&self, input: Self::Domain) -> Self::Image;

    fn inv_f(&self, input: Self::Image) -> Self::Domain;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BijectionFn<F, InvF, D, I> {
    f: F,
    inv_f: InvF,

    _domain: core::marker::PhantomData<D>,
    _image: core::marker::PhantomData<I>,
}

impl<F, InvF, D, I> Bijection for BijectionFn<F, InvF, D, I>
where
    F: Fn(D) -> I,
    InvF: Fn(I) -> D,
{
    type Domain = D;
    type Image = I;

    fn f(&self, input: Self::Domain) -> Self::Image {
        (self.f)(input)
    }
    fn inv_f(&self, input: Self::Image) -> Self::Domain {
        (self.inv_f)(input)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InvertedBijection<B>(pub B);

impl<B> Bijection for InvertedBijection<B>
where
    B: Bijection,
{
    type Domain = B::Image;
    type Image = B::Domain;
    fn f(&self, input: Self::Domain) -> Self::Image {
        self.0.inv_f(input)
    }

    fn inv_f(&self, input: Self::Image) -> Self::Domain {
        self.0.f(input)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TermBijectionOverlay<B, G> {
    bijection: B,
    view: G,
}

impl<B, G> TermBijectionOverlay<B, G> {
    pub fn new(view: G, bijection: B) -> Self {
        Self { bijection, view }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BijectedFilter<B, F> {
    bijection: B,
    filter: F,
}

impl<B, F> TripleFilter for BijectedFilter<B, F>
where
    B: Bijection<Domain = Arc<Term>, Image = Arc<Term>> + Send + Sync + Clone,
    F: TripleFilter,
{
    fn filter(&self, triple: crate::prelude::TripleRef) -> bool {
        let triple = triple.to_triple();
        let triple = Triple::new(
            self.bijection.inv_f(triple.subject_arc()),
            self.bijection.inv_f(triple.predicate_arc()),
            self.bijection.inv_f(triple.object_arc()),
        )
        .unwrap();
        self.filter.filter(triple.as_triple_ref())
    }
}

#[derive(Clone, PartialEq, Eq, Debug)]
pub struct BijectedMatches {
    is_empty: bool,
    subject: Option<Arc<Term>>,
    predicate: Option<Arc<Term>>,
    object: Option<Arc<Term>>,
}

impl BijectedMatches {
    pub fn new<M, B>(matches: M, bijection: B) -> Self
    where
        M: Matches,
        B: Bijection<Domain = Arc<Term>, Image = Arc<Term>>,
    {
        let matches = matches.get();
        Self {
            is_empty: matches.is_empty(),
            subject: matches
                .subject
                .map(|t| bijection.inv_f(Arc::new(t.clone()))),
            predicate: matches
                .predicate
                .map(|t| bijection.inv_f(Arc::new(t.clone()))),
            object: matches.object.map(|t| bijection.inv_f(Arc::new(t.clone()))),
        }
    }
}

impl Matches for BijectedMatches {
    fn is_empty(&self) -> bool {
        self.is_empty
    }

    fn get(&self) -> super::FinalMatches<'_> {
        super::FinalMatches {
            empty: self.is_empty,
            subject: self.subject.as_deref(),
            predicate: self.predicate.as_deref(),
            object: self.object.as_deref(),
        }
    }
}

impl<B, G> GraphView for TermBijectionOverlay<B, G>
where
    B: Bijection<Domain = Arc<Term>, Image = Arc<Term>> + Clone + Send + Unpin + Sync,
    G: GraphView,
{
    type Stream = TermBijectionStream<B, G::Stream>;

    async fn stream(self) -> Result<Self::Stream, GraphError> {
        Ok(TermBijectionStream {
            bijection: self.bijection,
            stream: self.view.stream().await?,
        })
    }

    type Filter<T>
        = TermBijectionOverlay<B, G::Filter<BijectedFilter<B, T>>>
    where
        T: super::TripleFilter + Unpin;

    async fn filter<F>(self, filter: F) -> Result<Self::Filter<F>, GraphError>
    where
        F: super::TripleFilter + Unpin,
    {
        Ok(TermBijectionOverlay {
            view: self
                .view
                .filter(BijectedFilter {
                    bijection: self.bijection.clone(),
                    filter,
                })
                .await?,
            bijection: self.bijection,
        })
    }

    type Matches<M>
        = TermBijectionOverlay<B, G::Matches<BijectedMatches>>
    where
        M: super::Matches + Unpin;

    async fn matches<M>(
        self,
        matches: M,
        limit: Option<usize>,
    ) -> Result<Self::Matches<BijectedMatches>, GraphError>
    where
        M: super::Matches + Unpin,
    {
        Ok(TermBijectionOverlay {
            view: self
                .view
                .matches(BijectedMatches::new(matches, self.bijection.clone()), limit)
                .await?,
            bijection: self.bijection,
        })
    }

    type CBD<T>
        = TermBijectionOverlay<B, G::CBD<Arc<Term>>>
    where
        T: AsRef<Term> + Clone + Send;

    async fn cbd<T>(self, subject: T) -> Result<Self::CBD<T>, GraphError>
    where
        T: AsRef<Term> + Clone + Send,
    {
        Ok(TermBijectionOverlay {
            view: self
                .view
                .cbd(self.bijection.inv_f(Arc::new(subject.as_ref().clone())))
                .await?,
            bijection: self.bijection,
        })
    }

    crate::impl_default_streams!();
    crate::impl_default_streams_dedup!();

    async fn subject(
        self,
        predicate: Option<&Term>,
        object: Option<&Term>,
    ) -> Result<Option<Arc<Term>>, GraphError> {
        let predicate = predicate.map(|term| self.bijection.inv_f(Arc::new(term.clone())));
        let object = object.map(|term| self.bijection.inv_f(Arc::new(term.clone())));

        self.view
            .subject(predicate.as_deref(), object.as_deref())
            .await
            .map(|t| t.map(|term| self.bijection.f(term)))
    }

    async fn predicate(
        self,
        subject: Option<&Term>,
        object: Option<&Term>,
    ) -> Result<Option<Arc<Term>>, GraphError> {
        let subject = subject.map(|term| self.bijection.inv_f(Arc::new(term.clone())));
        let object = object.map(|term| self.bijection.inv_f(Arc::new(term.clone())));

        self.view
            .predicate(subject.as_deref(), object.as_deref())
            .await
            .map(|t| t.map(|term| self.bijection.f(term)))
    }

    async fn object(
        self,
        subject: Option<&Term>,
        predicate: Option<&Term>,
    ) -> Result<Option<Arc<Term>>, GraphError> {
        let subject = subject.map(|term| self.bijection.inv_f(Arc::new(term.clone())));
        let predicate = predicate.map(|term| self.bijection.inv_f(Arc::new(term.clone())));

        self.view
            .object(subject.as_deref(), predicate.as_deref())
            .await
            .map(|t| t.map(|term| self.bijection.f(term)))
    }

    async fn contains(self, triple: crate::prelude::TripleRef<'_>) -> Result<bool, GraphError> {
        let triple = triple.to_triple();
        let subject = self.bijection.inv_f(triple.subject_arc());
        let predicate = self.bijection.inv_f(triple.predicate_arc());
        let object = self.bijection.inv_f(triple.object_arc());
        let mt = Triple::new(subject, predicate, object).unwrap();
        self.view.contains(mt.as_triple_ref()).await
    }

    fn default_triple_size_hint() -> usize {
        G::default_triple_size_hint()
    }

    fn default_buf_size_hint() -> usize {
        G::default_buf_size_hint()
    }

    fn triple_size_hint(&self) -> usize {
        self.view.triple_size_hint()
    }

    fn buf_size_hint(&self) -> usize {
        self.view.buf_size_hint()
    }
}

pub struct TermBijectionStream<B, S> {
    bijection: B,
    stream: S,
}

impl<B, S> Stream for TermBijectionStream<B, S>
where
    B: Bijection<Image = Arc<Term>, Domain = Arc<Term>> + Unpin,
    S: Stream<Item = Result<Arc<Triple>, GraphError>> + Unpin,
{
    type Item = Result<Arc<Triple>, GraphError>;

    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;

        match s.stream.poll_next_unpin(cx) {
            core::task::Poll::Ready(Some(Ok(t))) => core::task::Poll::Ready(Some(Ok(Triple::new(
                self.bijection.f(t.subject_arc()),
                self.bijection.f(t.predicate_arc()),
                self.bijection.f(t.object_arc()),
            )
            .unwrap()))),
            err @ core::task::Poll::Ready(Some(Err(_))) => err,
            end_or_pending => end_or_pending,
        }
    }
}
