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

import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import org.ggp.base.util.gdl.GdlVisitor;
import org.ggp.base.util.gdl.GdlVisitors;
import org.ggp.base.util.gdl.grammar.Gdl;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
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.transforms.GdlCleaner;
import org.ggp.base.util.gdl.transforms.VariableConstrainer;

import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

public class SentenceFormModelFactory {
    private SentenceFormModelFactory() {
    }

    /**
     * Creates a SentenceFormModel for the given game description.
     *
     * It is recommended to use the {@link GdlCleaner} on the game
     * description before constructing this model, to prevent some
     * common problems with slightly invalid game descriptions.
     *
     * It is also recommended to use the {@link VariableConstrainer}
     * on the description before using this. If the description allows
     * for function-valued variables, some aspects of the model,
     * including the dependency graph, may be incorrect.
     */
    public static ImmutableSentenceFormModel create(List<Gdl> description) throws InterruptedException {
        ImmutableList<Gdl> gameRules = ImmutableList.copyOf(description);
        ImmutableSet<SentenceForm> sentenceForms = getSentenceForms(gameRules);
        ImmutableSetMultimap<SentenceForm, GdlRule> rulesByForm = getRulesByForm(gameRules, sentenceForms);
        ImmutableSetMultimap<SentenceForm, GdlSentence> trueSentencesByForm = getTrueSentencesByForm(gameRules, sentenceForms);
        ImmutableSetMultimap<SentenceForm, SentenceForm> dependencyGraph = getDependencyGraph(sentenceForms, rulesByForm);
        ImmutableSet<SentenceForm> constantSentenceForms = getConstantSentenceForms(sentenceForms, dependencyGraph);
        ImmutableSet<SentenceForm> independentSentenceForms = getIndependentSentenceForms(sentenceForms, dependencyGraph);

        return new ImmutableSentenceFormModel(
                gameRules,
                sentenceForms,
                constantSentenceForms,
                independentSentenceForms,
                dependencyGraph,
                rulesByForm,
                trueSentencesByForm);
    }

    private static ImmutableSet<SentenceForm> getIndependentSentenceForms(
            ImmutableSet<SentenceForm> sentenceForms,
            ImmutableSetMultimap<SentenceForm, SentenceForm> dependencyGraph) {
        SetMultimap<SentenceForm, SentenceForm> augmentedGraph = augmentGraphWithLanguageRules(dependencyGraph, sentenceForms);
        ImmutableSet<SentenceForm> moveDependentSentenceForms =
                DependencyGraphs.getMatchingAndDownstream(sentenceForms, augmentedGraph,
                        SentenceForms.DOES_PRED);
        return ImmutableSet.copyOf(Sets.difference(sentenceForms, moveDependentSentenceForms));
    }

    private static ImmutableSet<SentenceForm> getConstantSentenceForms(
            ImmutableSet<SentenceForm> sentenceForms,
            ImmutableSetMultimap<SentenceForm, SentenceForm> dependencyGraph) {
        SetMultimap<SentenceForm, SentenceForm> augmentedGraph = augmentGraphWithLanguageRules(dependencyGraph, sentenceForms);
        ImmutableSet<SentenceForm> changingSentenceForms =
                DependencyGraphs.getMatchingAndDownstream(sentenceForms, augmentedGraph,
                        Predicates.or(SentenceForms.TRUE_PRED, SentenceForms.DOES_PRED));
        return ImmutableSet.copyOf(Sets.difference(sentenceForms, changingSentenceForms));
    }

    /**
     * Modifies the graph by adding dependencies corresponding to language rules
     * that apply in a looser sense: TRUE forms depend on NEXT forms and DOES
     * forms depend on LEGAL forms.
     */
    private static SetMultimap<SentenceForm, SentenceForm> augmentGraphWithLanguageRules(
            ImmutableSetMultimap<SentenceForm, SentenceForm> dependencyGraph, ImmutableSet<SentenceForm> sentenceForms) {
        SetMultimap<SentenceForm, SentenceForm> newGraph = HashMultimap.create();
        newGraph.putAll(dependencyGraph);
        for (SentenceForm form : sentenceForms) {
            if (form.getName() == GdlPool.TRUE) {
                SentenceForm nextForm = form.withName(GdlPool.NEXT);
                if (sentenceForms.contains(nextForm)) {
                    newGraph.put(form, nextForm);
                }
            } else if (form.getName() == GdlPool.DOES) {
                SentenceForm legalForm = form.withName(GdlPool.LEGAL);
                if (sentenceForms.contains(legalForm)) {
                    newGraph.put(form, legalForm);
                }
            }
        }
        return newGraph;
    }

    private static ImmutableSetMultimap<SentenceForm, SentenceForm> getDependencyGraph(
            ImmutableSet<SentenceForm> sentenceForms,
            ImmutableSetMultimap<SentenceForm, GdlRule> rulesByForm) {
        SetMultimap<SentenceForm, SentenceForm> dependencyGraph = HashMultimap.create();
        for(Entry<SentenceForm, GdlRule> entry : rulesByForm.entries()) {
            SentenceForm head = entry.getKey();
            GdlRule rule = entry.getValue();
            for(GdlLiteral bodyLiteral : rule.getBody()) {
                dependencyGraph.putAll(head, getSentenceFormsInBody(bodyLiteral, sentenceForms));
            }
        }
        return ImmutableSetMultimap.copyOf(dependencyGraph);
    }

    private static Set<SentenceForm> getSentenceFormsInBody(
            GdlLiteral bodyLiteral, final ImmutableSet<SentenceForm> sentenceForms) {
        final Set<SentenceForm> forms = new HashSet<SentenceForm>();
        GdlVisitors.visitAll(bodyLiteral, new GdlVisitor() {
            @Override
            public void visitSentence(GdlSentence sentence) {
                for (SentenceForm form : sentenceForms) {
                    if (form.matches(sentence)) {
                        forms.add(form);
                    }
                }
            }
        });
        return forms;
    }

    private static ImmutableSetMultimap<SentenceForm, GdlSentence> getTrueSentencesByForm(
            ImmutableList<Gdl> gameRules,
            ImmutableSet<SentenceForm> sentenceForms) {
        ImmutableSetMultimap.Builder<SentenceForm, GdlSentence> builder =
                ImmutableSetMultimap.builder();

        for(Gdl gdl : gameRules) {
            if(gdl instanceof GdlSentence) {
                GdlSentence sentence = (GdlSentence) gdl;
                for (SentenceForm form : sentenceForms) {
                    if(form.matches(sentence)) {
                        builder.put(form, sentence);
                        break;
                    }
                }
            }
        }

        return builder.build();
    }

    private static ImmutableSetMultimap<SentenceForm, GdlRule> getRulesByForm(
            ImmutableList<Gdl> gameRules,
            ImmutableSet<SentenceForm> sentenceForms) {
        ImmutableSetMultimap.Builder<SentenceForm, GdlRule> builder =
                ImmutableSetMultimap.builder();

        for(Gdl gdl : gameRules) {
            if(gdl instanceof GdlRule) {
                GdlRule rule = (GdlRule) gdl;
                for (SentenceForm form : sentenceForms) {
                    if(form.matches(rule.getHead())) {
                        builder.put(form, rule);
                        break;
                    }
                }
            }
        }

        return builder.build();
    }

    private static ImmutableSet<SentenceForm> getSentenceForms(
            ImmutableList<Gdl> gameRules) throws InterruptedException {
        return new SentenceFormsFinder(gameRules).findSentenceForms();
    }
}
