//! Query interface over classified ontologies (petgraph-backed hierarchy views).

mod graph;

use ontologos_core::{EntityId, Ontology, Taxonomy};
use thiserror::Error;

pub use graph::TaxonomyGraph;

/// Result type for query operations.
pub type Result<T> = std::result::Result<T, Error>;

/// Query errors.
#[derive(Debug, Error)]
pub enum Error {
    #[error("unknown entity {0:?}")]
    UnknownEntity(EntityId),
    #[error(transparent)]
    Core(#[from] ontologos_core::Error),
}

/// Query handle over a classified ontology taxonomy.
#[derive(Debug)]
pub struct QueryEngine<'a> {
    ontology: &'a Ontology,
    taxonomy: &'a Taxonomy,
    graph: TaxonomyGraph,
}

impl<'a> QueryEngine<'a> {
    #[must_use]
    pub fn new(ontology: &'a Ontology, taxonomy: &'a Taxonomy) -> Self {
        Self {
            ontology,
            taxonomy,
            graph: TaxonomyGraph::from_taxonomy(taxonomy),
        }
    }

    pub fn direct_subclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
        self.ensure_class(class)?;
        Ok(self.graph.direct_subclasses(class))
    }

    pub fn direct_superclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
        self.ensure_class(class)?;
        Ok(self.graph.direct_superclasses(class))
    }

    pub fn is_subsumed(&self, sub: EntityId, sup: EntityId) -> Result<bool> {
        self.ensure_class(sub)?;
        self.ensure_class(sup)?;
        Ok(self.graph.is_subsumed(sub, sup))
    }

    pub fn equivalent_classes(&self, class: EntityId) -> Result<Option<Vec<EntityId>>> {
        self.ensure_class(class)?;
        Ok(self
            .taxonomy
            .equivalent_classes(class)
            .map(<[EntityId]>::to_vec))
    }

    pub fn unsatisfiable_classes(&self) -> Vec<EntityId> {
        self.taxonomy.unsatisfiable.clone()
    }

    pub fn lookup(&self, iri: &str) -> Option<EntityId> {
        self.ontology.lookup_entity(iri)
    }

    fn ensure_class(&self, class: EntityId) -> Result<()> {
        let record = self.ontology.entity(class)?;
        if record.kind != ontologos_core::EntityKind::Class {
            return Err(Error::UnknownEntity(class));
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use ontologos_core::{Axiom, EntityKind, Ontology, Taxonomy};

    use super::*;

    #[test]
    fn query_direct_subclasses_from_taxonomy() {
        let mut ontology = Ontology::new();
        let a = ontology
            .entity_id("http://ex.org/A", EntityKind::Class)
            .unwrap();
        let b = ontology
            .entity_id("http://ex.org/B", EntityKind::Class)
            .unwrap();
        ontology
            .add_axiom(Axiom::SubClassOf {
                subclass: a,
                superclass: b,
            })
            .unwrap();

        let taxonomy = Taxonomy {
            subsumptions: vec![(a, b)],
            ..Taxonomy::default()
        };
        let engine = QueryEngine::new(&ontology, &taxonomy);
        let subs = engine.direct_subclasses(b).expect("subs");
        assert!(subs.contains(&a));
    }

    #[test]
    fn is_subsumed_transitive() {
        let mut ontology = Ontology::new();
        let a = ontology
            .entity_id("http://ex.org/A", EntityKind::Class)
            .unwrap();
        let b = ontology
            .entity_id("http://ex.org/B", EntityKind::Class)
            .unwrap();
        let c = ontology
            .entity_id("http://ex.org/C", EntityKind::Class)
            .unwrap();
        ontology
            .add_axiom(Axiom::SubClassOf {
                subclass: a,
                superclass: b,
            })
            .unwrap();
        ontology
            .add_axiom(Axiom::SubClassOf {
                subclass: b,
                superclass: c,
            })
            .unwrap();

        let taxonomy = Taxonomy {
            subsumptions: vec![(a, b), (b, c)],
            ..Taxonomy::default()
        };
        let engine = QueryEngine::new(&ontology, &taxonomy);
        assert!(engine.is_subsumed(a, c).expect("subsumed"));
    }
}
