package uk.ac.manchester.cs.factplusplusad;

import java.util.Collection;
import java.util.Optional;
import java.util.Set;

import org.semanticweb.owlapitools.decomposition.AxiomWrapper;
import org.semanticweb.owlapitools.decomposition.OntologyAtom;

import uk.ac.manchester.cs.owlapi.modularity.ModuleType;

/**
 * Atomic decomposer for an ontology.
 */
class AtomicDecomposer {

    /**
     * atomic structure to build
     */
    AOStructure aos = null;
    /**
     * modularizer to build modules
     */
    Modularizer modularizer;
    /**
     * tautologies of the ontology
     */
    Set<AxiomWrapper> tautologies;
    /**
     * fake atom that represents the whole ontology
     */
    OntologyAtom rootAtom = null;
    /**
     * module type for current AOS creation
     */
    ModuleType type;

    /**
     * @param m modulariser
     */
    AtomicDecomposer(Modularizer m) {
        modularizer = m;
    }

    /**
     * restore all tautologies back
     */
    void restoreTautologies() {
        tautologies.forEach(p -> p.setUsed(true));
    }

    /**
     * @return already created atomic structure
     */
    AOStructure getAOS() {
        return aos;
    }

    /**
     * @return number of performed locality checks
     */
    long getLocChekNumber() {
        return modularizer.getNChecks();
    }

    /**
     * Remove tautologies (axioms that are always local) from the ontology temporarily.
     *
     * @param o ontology
     */
    void removeTautologies(Collection<AxiomWrapper> o) {
        // we might use it for another decomposition
        tautologies.clear();
        for (AxiomWrapper p : o) {
            if (p.isUsed() && modularizer.isTautology(p.getAxiom(), type)) {
                tautologies.add(p);
                p.setUsed(false);
            }
        }
    }

    /**
     * Build a module for given axiom AX; use parent atom's module as a base for the module search.
     *
     * @param sig signature
     * @param parent parent atom
     * @return module atom
     */
    Optional<OntologyAtom> buildModule(Signature sig, OntologyAtom parent) {
        // build a module for a given signature
        modularizer.extract(parent.getModule(), sig, type);
        Collection<AxiomWrapper> module = modularizer.getModule();
        // if module is empty (empty bottom atom) -- do nothing
        if (module.isEmpty()) {
            return Optional.empty();
        }
        // check if the module corresponds to a PARENT one; modules are the same
        // iff their sizes are the same
        if (parent != rootAtom && module.size() == parent.getModule().size()) {
            return Optional.of(parent);
        }
        // create new atom with that module
        OntologyAtom atom = aos.newAtom();
        atom.setModule(module);
        return Optional.of(atom);
    }

    /**
     * Create atom for given axiom AX; use parent atom's module as a base for the module search.
     *
     * @param ax axiom
     * @param parent parent atom
     * @return atom
     */
    OntologyAtom createAtom(AxiomWrapper ax, OntologyAtom parent) {
        // check whether axiom already has an atom
        if (ax.getAtom().isPresent()) {
            return ax.getAtom().get();
        }
        // build an atom: use a module to find atomic dependencies
        Optional<OntologyAtom> atom = buildModule(new Signature(ax.signature()), parent);
        // no empty modules should be here
        assert atom.isPresent();
        // register axiom as a part of an atom
        atom.get().addAxiom(ax);
        // if atom is the same as parent -- nothing more to do
        if (atom.get() == parent) {
            return parent;
        }
        // not the same as parent: for all atom's axioms check their atoms and
        // make ATOM depend on them
        for (AxiomWrapper q : atom.get().getModule()) {
            if (q != ax) {
                atom.get().addDepAtom(createAtom(q, atom.get()));
            }
        }
        return atom.get();
    }

    /**
     * @param o ontology
     * @param t module type
     * @return atomic structure for given module type T
     */
    AOStructure getAOS(Collection<AxiomWrapper> o, ModuleType t) {
        // remember the type of the module
        type = t;
        // prepare a new AO structure
        aos = new AOStructure();
        // init semantic locality checker
        modularizer.preprocessOntology(o);
        // we don't need tautologies here
        removeTautologies(o);
        // init the root atom
        rootAtom = new OntologyAtom();
        rootAtom.setModule(o);
        // build the "bottom" atom for an empty signature
        Optional<OntologyAtom> bottomAtom = buildModule(new Signature(), rootAtom);
        if (bottomAtom.isPresent()) {
            for (AxiomWrapper q : bottomAtom.get().getModule()) {
                bottomAtom.get().addAxiom(q);
            }
        }
        // create atoms for all the axioms in the ontology
        for (AxiomWrapper p : o) {
            if (p.isUsed() && !p.getAtom().isPresent()) {
                createAtom(p, rootAtom);
            }
        }
        // restore tautologies in the ontology
        restoreTautologies();
        // clear the root atom
        rootAtom = null;
        // reduce graph
        aos.reduceGraph();
        return aos;
    }
}
