use crate::{
    graphs::{FinalMatches as FinalTripleMatches, Matches as TripleMatches, MatchesFilter},
    quads::filter::QuadFilter,
    terms::Term,
};

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FinalMatches<'a> {
    pub subject: Option<&'a Term>,
    pub predicate: Option<&'a Term>,
    pub object: Option<&'a Term>,
    pub graph_name: Option<Option<&'a Term>>,
    pub empty: bool,
}

pub trait QuadMatches: Send + Clone + Sync {
    fn get(&self) -> FinalMatches;
    fn is_empty(&self) -> bool {
        self.get().empty
    }
}

impl<M> QuadMatches for M
where
    M: TripleMatches,
{
    fn get(&self) -> FinalMatches {
        let FinalTripleMatches {
            subject,
            predicate,
            object,
            empty,
        } = <M as TripleMatches>::get(self);

        FinalMatches {
            subject,
            predicate,
            object,
            graph_name: None,
            empty,
        }
    }

    fn is_empty(&self) -> bool {
        <M as TripleMatches>::is_empty(self)
    }
}

impl QuadMatches for FinalMatches<'_> {
    fn get(&self) -> FinalMatches {
        *self
    }
    fn is_empty(&self) -> bool {
        self.empty
    }
}

impl<M> QuadFilter for MatchesFilter<M>
where
    M: QuadMatches,
{
    fn filter(&self, quad: crate::quads::QuadRef<'_>) -> bool {
        let matches = self.0.get();
        !matches.empty
            && (matches.subject.map(|s| s == quad.subject()).unwrap_or(true)
                && matches
                    .predicate
                    .map(|s| s == quad.predicate())
                    .unwrap_or(true)
                && matches.object.map(|t| t == quad.object()).unwrap_or(true)
                && matches
                    .graph_name
                    .map(|t| t == quad.graph_name())
                    .unwrap_or(true))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct JoinedMatches<M1, M2> {
    a: M1,
    b: M2,
}

impl<M1, M2> JoinedMatches<M1, M2> {
    pub fn new(a: M1, b: M2) -> Self {
        Self { a, b }
    }
}

impl<M1, M2> QuadMatches for JoinedMatches<M1, M2>
where
    M1: QuadMatches,
    M2: QuadMatches,
{
    fn get(&self) -> FinalMatches {
        let matches_a = self.a.get();
        let matches_b = self.b.get();

        if matches_a.empty && matches_b.empty {
            return FinalMatches {
                empty: true,
                ..Default::default()
            };
        }

        let subject = match (matches_a.subject, matches_b.subject) {
            (None, Some(s)) | (Some(s), None) => Some(s),
            (Some(_), Some(_)) => {
                return FinalMatches {
                    empty: true,
                    ..Default::default()
                }
            }
            (None, None) => None,
        };
        let predicate = match (matches_a.predicate, matches_b.predicate) {
            (None, Some(p)) | (Some(p), None) => Some(p),
            (Some(_), Some(_)) => {
                return FinalMatches {
                    empty: true,
                    ..Default::default()
                }
            }
            (None, None) => None,
        };
        let object = match (matches_a.object, matches_b.object) {
            (None, Some(o)) | (Some(o), None) => Some(o),
            (Some(_), Some(_)) => {
                return FinalMatches {
                    empty: true,
                    ..Default::default()
                }
            }
            (None, None) => None,
        };

        let graph_name = match (matches_a.graph_name, matches_b.graph_name) {
            (None, Some(qt)) | (Some(qt), None) => Some(qt),
            (Some(_), Some(_)) => {
                return FinalMatches {
                    empty: true,
                    ..Default::default()
                }
            }
            (None, None) => None,
        };

        FinalMatches {
            subject,
            predicate,
            object,
            graph_name,
            ..Default::default()
        }
    }
}

pub type MatchesTuple<'a, 'b, 'c, 'd> = (
    Option<&'a Term>,
    Option<&'b Term>,
    Option<&'c Term>,
    Option<Option<&'d Term>>,
);

impl QuadMatches for MatchesTuple<'_, '_, '_, '_> {
    fn get(&self) -> FinalMatches {
        FinalMatches {
            subject: self.0,
            predicate: self.1,
            object: self.2,
            graph_name: self.3,
            empty: false,
        }
    }
    fn is_empty(&self) -> bool {
        false
    }
}
