package org.ggp.base.util.gdl.transforms;

import java.util.List;
import java.util.Set;

import org.ggp.base.util.concurrency.ConcurrencyUtils;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlRule;
import org.ggp.base.util.gdl.grammar.GdlSentence;
import org.ggp.base.util.gdl.grammar.GdlTerm;
import org.ggp.base.util.gdl.grammar.GdlVariable;
import org.ggp.base.util.gdl.model.DependencyGraphs;
import org.ggp.base.util.gdl.model.SentenceDomainModel;
import org.ggp.base.util.gdl.model.SentenceForm;
import org.ggp.base.util.gdl.model.SentenceFormModel;
import org.ggp.base.util.prover.aima.AimaProver;
import org.ggp.base.util.reasoner.gdl.GdlChainingReasoner;
import org.ggp.base.util.reasoner.gdl.GdlSentenceSet;

import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;

public class ConstantCheckerFactory {
    /**
     * Precomputes the true sentences in every constant sentence form in the given
     * sentence model and returns the results in the form of a ConstantChecker.
     *
     * The implementation uses a forward-chaining reasoner.
     *
     * For accurate results, the rules used should have had the {@link VariableConstrainer}
     * transformation applied to them.
     *
     * On average, this approach is more efficient than {@link #createWithProver(SentenceFormModel)}.
     */
    public static ImmutableConstantChecker createWithForwardChaining(SentenceDomainModel model) throws InterruptedException {
        GdlChainingReasoner reasoner = GdlChainingReasoner.create(model);
        GdlSentenceSet sentencesByForm = reasoner.getConstantSentences();
        addSentencesTrueByRulesDifferentially(sentencesByForm, model, reasoner);
        return ImmutableConstantChecker.create(model,
                Multimaps.filterKeys(sentencesByForm.getSentences(), Predicates.in(model.getConstantSentenceForms())));
    }

    private static void addSentencesTrueByRulesDifferentially(
            GdlSentenceSet sentencesByForm,
            SentenceDomainModel domainModel, GdlChainingReasoner reasoner) throws InterruptedException {
        SentenceFormModel model = domainModel;
        Set<SentenceForm> constantForms = model.getConstantSentenceForms();
        //Find the part of the dependency graph dealing only with the constant forms.
        Multimap<SentenceForm, SentenceForm> dependencySubgraph =
                Multimaps.filterKeys(model.getDependencyGraph(), Predicates.in(constantForms));
        dependencySubgraph = Multimaps.filterValues(model.getDependencyGraph(), Predicates.in(constantForms));
        dependencySubgraph = ImmutableMultimap.copyOf(dependencySubgraph);
        List<Set<SentenceForm>> ordering = DependencyGraphs.toposortSafe(constantForms, dependencySubgraph);

        for (Set<SentenceForm> stratum : ordering) {
            // One non-differential pass, collecting the changes
            GdlSentenceSet newlyTrueSentences = GdlSentenceSet.create();
            for (SentenceForm form : stratum) {
                for (GdlRule rule : model.getRules(form)) {
                    GdlSentenceSet ruleResults =
                            reasoner.getRuleResults(rule, domainModel, sentencesByForm);
                    if (!reasoner.isSubsetOf(sentencesByForm, ruleResults)) {
                        sentencesByForm = reasoner.getUnion(sentencesByForm, ruleResults);
                        newlyTrueSentences = reasoner.getUnion(newlyTrueSentences, ruleResults);
                    }
                }
            }

            // Now a lot of differential passes to deal with recursion efficiently
            boolean somethingChanged = true;
            while (somethingChanged) {
                somethingChanged = false;
                GdlSentenceSet newStuffInThisPass = GdlSentenceSet.create();
                for (SentenceForm form : stratum) {
                    for (GdlRule rule : model.getRules(form)) {
                        GdlSentenceSet ruleResults =
                                reasoner.getRuleResultsForNewSentences(rule, domainModel, sentencesByForm,
                                        newlyTrueSentences);
                        if (!reasoner.isSubsetOf(sentencesByForm, ruleResults)) {
                            somethingChanged = true;
                            newStuffInThisPass = reasoner.getUnion(newStuffInThisPass, ruleResults);
                        }
                    }
                }
                sentencesByForm = reasoner.getUnion(sentencesByForm, newStuffInThisPass);
                newlyTrueSentences = newStuffInThisPass;
            }
        }
    }

    /**
     * Precomputes the true sentences in every constant sentence form in the given
     * sentence model and returns the results in the form of a ConstantChecker.
     *
     * The implementation uses a backwards-chaining theorem prover.
     *
     * In most (but not all) cases, {@link #createWithForwardChaining(SentenceDomainModel)}
     * is more efficient.
     */
    public static ImmutableConstantChecker createWithProver(SentenceFormModel model) throws InterruptedException {
        Multimap<SentenceForm, GdlSentence> sentencesByForm = HashMultimap.create();
        addSentencesTrueByRules(sentencesByForm, model);
        return ImmutableConstantChecker.create(model, sentencesByForm);
    }

    private static void addSentencesTrueByRules(
            Multimap<SentenceForm, GdlSentence> sentencesByForm,
            SentenceFormModel model) throws InterruptedException {
        AimaProver prover = new AimaProver(model.getDescription());
        for (SentenceForm form : model.getConstantSentenceForms()) {
            GdlSentence query = form.getSentenceFromTuple(getVariablesTuple(form.getTupleSize()));
            for (GdlSentence result : prover.askAll(query, ImmutableSet.<GdlSentence>of())) {
                ConcurrencyUtils.checkForInterruption();
                //Variables may end up being replaced with functions, which is not
                //what we want here, so we have to double-check that the form is correct.
                if (form.matches(result)) {
                    sentencesByForm.put(form, result);
                }
            }
        }
    }

    private static List<? extends GdlTerm> getVariablesTuple(int tupleSize) {
        List<GdlVariable> varsTuple = Lists.newArrayList();
        for (int i = 0; i < tupleSize; i++) {
            varsTuple.add(GdlPool.getVariable("?" + i));
        }
        return varsTuple;
    }
}
