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

use super::TripleFilter;

#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug, Default)]
pub struct FinalMatches<'a> {
    pub empty: bool,

    pub subject: Option<&'a Term>,
    pub predicate: Option<&'a Term>,
    pub object: Option<&'a Term>,
}

pub trait Matches: Clone + Send + Sync {
    fn get(&self) -> FinalMatches<'_>;

    fn is_empty(&self) -> bool {
        let matches = self.get();
        matches.empty
    }
}

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

impl<T> TripleFilter for MatchesFilter<T>
where
    T: Matches,
{
    fn filter(&self, triple: TripleRef) -> bool {
        let matches = self.0.get();
        !matches.empty
            && (matches
                .subject
                .map(|s| s == triple.subject())
                .unwrap_or(true)
                && matches
                    .predicate
                    .map(|s| s == triple.predicate())
                    .unwrap_or(true)
                && matches.object.map(|t| t == triple.object()).unwrap_or(true))
    }
}

impl<'a> Matches for FinalMatches<'a> {
    fn get(&self) -> FinalMatches<'a> {
        *self
    }
}

impl Matches for () {
    fn get(&self) -> FinalMatches<'_> {
        Default::default()
    }
}

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

impl<'a> Matches for SimpleMatches<'a> {
    fn get(&self) -> FinalMatches<'a> {
        FinalMatches {
            empty: false,
            subject: self.subject,
            predicate: self.predicate,
            object: self.object,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct YesMatches;

impl Matches for YesMatches {
    fn get(&self) -> FinalMatches<'_> {
        FinalMatches {
            empty: false,
            subject: None,
            predicate: None,
            object: None,
        }
    }
}

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

#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
pub struct SubjectMatch<'a> {
    subject: &'a Term,
}

impl<'a> SubjectMatch<'a> {
    pub fn new(subject: &'a Term) -> Self {
        Self { subject }
    }
}

impl<'a> Matches for SubjectMatch<'a> {
    fn get(&self) -> FinalMatches<'a> {
        FinalMatches {
            subject: Some(self.subject),
            ..Default::default()
        }
    }
}

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

#[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 {
        JoinedMatches { a, b }
    }
}

impl<M1, M2> Matches for JoinedMatches<M1, M2>
where
    M1: Matches,
    M2: Matches,
{
    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,
        };

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