/**
 *
 */
package org.ggp.base.util.gdl.model.assignments;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.grammar.GdlConstant;
import org.ggp.base.util.gdl.grammar.GdlDistinct;
import org.ggp.base.util.gdl.grammar.GdlFunction;
import org.ggp.base.util.gdl.grammar.GdlLiteral;
import org.ggp.base.util.gdl.grammar.GdlPool;
import org.ggp.base.util.gdl.grammar.GdlProposition;
import org.ggp.base.util.gdl.grammar.GdlRelation;
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.SentenceForm;
import org.ggp.base.util.gdl.model.SimpleSentenceForm;
import org.ggp.base.util.gdl.transforms.CommonTransforms;
import org.ggp.base.util.gdl.transforms.ConstantChecker;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;


public class AssignmentsImpl implements Assignments {
    private boolean empty;
    private boolean allDone = false;
    //Contains all the assignments of variables we could make
    private Map<GdlVariable, GdlConstant> headAssignment;

    private List<GdlVariable> varsToAssign;
    private List<ImmutableList<GdlConstant>> valuesToIterate;
    private List<AssignmentFunction> valuesToCompute;
    private List<Integer> indicesToChangeWhenNull; //See note below
    private List<GdlDistinct> distincts;
    private List<GdlVariable> varsToChangePerDistinct; //indexing same as distincts

    /*
     * What does indicesToChangeWhenNull do? Well, sometimes after incrementing
     * part of the iterator, we find that a function being used to define a slot
     * in the tuple has no value corresponding to its inputs (the inputs are
     * outside the function's domain). In that case, we set the value to null,
     * then leave it to the makeNextAssignmentValid() method to deal with it.
     * We want to increment something in the input, but we need to know what
     * in the input we should increment (i.e. which is the rightmost slot in
     * the function's input). This is recorded in indicesToChangeWhenNull. If
     * a slot is not defined by a function, then presumably it will not be null,
     * so its value here is unimportant. Setting its value to -1 would help
     * catch errors.
     */

    private List<ImmutableList<ImmutableList<GdlConstant>>> tuplesBySource; //indexed by conjunct
    private List<Integer> sourceDefiningSlot; //indexed by var slot
    private List<ImmutableList<Integer>> varsChosenBySource; //indexed by conjunct, then slot
    private List<ImmutableList<Boolean>> putDontCheckBySource; //indexed by conjunct, then slot

    /**
     * Creates an Assignments object that generates AssignmentIterators.
     * These can be used to efficiently iterate over all possible assignments
     * for variables in a given rule.
     *
     * @param headAssignment An assignment of variables whose values should be
     * fixed. May be empty.
     * @param rule The rule whose assignments we want to iterate over.
     * @param varDomains A map containing the possible values for each variable
     * in the rule. (All such values are GdlConstants.)
     * @param functionInfoMap
     * @param completedSentenceFormValues
     */
    public AssignmentsImpl(Map<GdlVariable, GdlConstant> headAssignment,
            GdlRule rule, Map<GdlVariable, Set<GdlConstant>> varDomains,
            Map<SentenceForm, ? extends FunctionInfo> functionInfoMap,
            Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) {
        empty = false;
        this.headAssignment = headAssignment;

        //We first have to find the remaining variables in the body
        varsToAssign = GdlUtils.getVariables(rule);
        //Remove all the duplicates; we do, however, want to keep the ordering
        List<GdlVariable> newVarsToAssign = new ArrayList<GdlVariable>();
        for(GdlVariable v : varsToAssign)
            if(!newVarsToAssign.contains(v))
                newVarsToAssign.add(v);
        varsToAssign = newVarsToAssign;
        varsToAssign.removeAll(headAssignment.keySet());
        //varsToAssign is set at this point

        //We see if iterating over entire tuples will give us a
        //better result, and we look for the best way of doing that.

        //Let's get the domains of the variables
        //Map<GdlVariable, Set<GdlConstant>> varDomains = model.getVarDomains(rule);
        //Since we're looking at a particular rule, we can do this one step better
        //by looking at the domain of the head, which may be more restrictive
        //and taking the intersections of the two domains where applicable
        //Map<GdlVariable, Set<GdlConstant>> headVarDomains = model.getVarDomainsInSentence(rule.getHead());

        //We can run the A* search for a good set of source conjuncts
        //at this point, then use the result to build the rest.
        Map<SentenceForm, Integer> completedSentenceFormSizes = new HashMap<SentenceForm, Integer>();
        if(completedSentenceFormValues != null)
            for(SentenceForm form : completedSentenceFormValues.keySet())
                completedSentenceFormSizes.put(form, completedSentenceFormValues.get(form).size());
        Map<GdlVariable, Integer> varDomainSizes = new HashMap<GdlVariable, Integer>();
        for(GdlVariable var : varDomains.keySet())
            varDomainSizes.put(var, varDomains.get(var).size());

        IterationOrderCandidate bestOrdering;
        bestOrdering = getBestIterationOrderCandidate(rule, varDomains,/*model,*/ functionInfoMap, completedSentenceFormSizes, headAssignment, false); //TODO: True here?

        //Want to replace next few things with order
        //Need a few extra things to handle the use of iteration over existing tuples
        varsToAssign = bestOrdering.getVariableOrdering();

        //For each of these vars, we have to find one or the other.
        //Let's start by finding all the domains, a task already done.
        valuesToIterate = Lists.newArrayListWithCapacity(varsToAssign.size());

        for(GdlVariable var : varsToAssign) {
            if(varDomains.containsKey(var)) {
                if(!varDomains.get(var).isEmpty())
                    valuesToIterate.add(ImmutableList.copyOf(varDomains.get(var)));
                else
                    valuesToIterate.add(ImmutableList.of(GdlPool.getConstant("0")));
            } else {
                valuesToIterate.add(ImmutableList.of(GdlPool.getConstant("0")));
            }
        }
        //Okay, the iteration-over-domain is done.
        //Now let's look at sourced iteration.
        sourceDefiningSlot = new ArrayList<Integer>(varsToAssign.size());
        for(int i = 0; i < varsToAssign.size(); i++) {
            sourceDefiningSlot.add(-1);
        }

        //We also need to convert values into tuples
        //We should do so while constraining to any constants in the conjunct
        //Let's convert the conjuncts
        List<GdlSentence> sourceConjuncts = bestOrdering.getSourceConjuncts();
        tuplesBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<List<GdlConstant>>>(sourceConjuncts.size());
        varsChosenBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<Integer>>(sourceConjuncts.size());
        putDontCheckBySource = Lists.newArrayListWithCapacity(sourceConjuncts.size());//new ArrayList<List<Boolean>>(sourceConjuncts.size());
        for(int j = 0; j < sourceConjuncts.size(); j++) {
            GdlSentence sourceConjunct = sourceConjuncts.get(j);
            SentenceForm form = SimpleSentenceForm.create(sourceConjunct);
            //flatten into a tuple
            List<GdlTerm> conjunctTuple = GdlUtils.getTupleFromSentence(sourceConjunct);
            //Go through the vars/constants in the tuple
            List<Integer> constraintSlots = new ArrayList<Integer>();
            List<GdlConstant> constraintValues = new ArrayList<GdlConstant>();
            List<Integer> varsChosen = new ArrayList<Integer>();
            List<Boolean> putDontCheck = new ArrayList<Boolean>();
            for(int i = 0; i < conjunctTuple.size(); i++) {
                GdlTerm term = conjunctTuple.get(i);
                if(term instanceof GdlConstant) {
                    constraintSlots.add(i);
                    constraintValues.add((GdlConstant) term);
                    //TODO: What if tuple size ends up being 0?
                    //Need to keep that in mind
                } else if(term instanceof GdlVariable) {
                    int varIndex = varsToAssign.indexOf(term);
                    varsChosen.add(varIndex);
                    if(sourceDefiningSlot.get(varIndex) == -1) {
                        //We define it
                        sourceDefiningSlot.set(varIndex, j);
                        putDontCheck.add(true);
                    } else {
                        //It's an overlap; we just check for consistency
                        putDontCheck.add(false);
                    }
                } else {
                    throw new RuntimeException("Function returned in tuple");
                }
            }
            varsChosenBySource.add(ImmutableList.copyOf(varsChosen));
            putDontCheckBySource.add(ImmutableList.copyOf(putDontCheck));

            //Now we put the tuples together
            //We use constraintSlots and constraintValues to check that the
            //tuples have compatible values
            Collection<GdlSentence> sentences = completedSentenceFormValues.get(form);
            List<ImmutableList<GdlConstant>> tuples = Lists.newArrayList();
            byTuple: for(GdlSentence sentence : sentences) {
                //Check that it doesn't conflict with our headAssignment
                if (!headAssignment.isEmpty()) {
                    Map<GdlVariable, GdlConstant> tupleAssignment = GdlUtils.getAssignmentMakingLeftIntoRight(sourceConjunct, sentence);
                    for (GdlVariable var : headAssignment.keySet()) {
                        if (tupleAssignment.containsKey(var)
                                && tupleAssignment.get(var) != headAssignment.get(var)) {
                            continue byTuple;
                        }
                    }
                }
                List<GdlConstant> longTuple = GdlUtils.getTupleFromGroundSentence(sentence);
                List<GdlConstant> shortTuple = new ArrayList<GdlConstant>(varsChosen.size());
                for(int c = 0; c < constraintSlots.size(); c++) {
                    int slot = constraintSlots.get(c);
                    GdlConstant value = constraintValues.get(c);
                    if(!longTuple.get(slot).equals(value))
                        continue byTuple;
                }
                int c = 0;
                for(int s = 0; s < longTuple.size(); s++) {
                    //constraintSlots is sorted in ascending order
                    if(c < constraintSlots.size()
                            && constraintSlots.get(c) == s)
                        c++;
                    else
                        shortTuple.add(longTuple.get(s));
                }
                //The tuple fits the source conjunct
                tuples.add(ImmutableList.copyOf(shortTuple));
            }
            //sortTuples(tuples); //Needed? Useful? Not sure. Probably not?
            tuplesBySource.add(ImmutableList.copyOf(tuples));
        }


        //We now want to see which we can give assignment functions to
        valuesToCompute = new ArrayList<AssignmentFunction>(varsToAssign.size());
        for(@SuppressWarnings("unused") GdlVariable var : varsToAssign) {
            valuesToCompute.add(null);
        }
        indicesToChangeWhenNull = new ArrayList<Integer>(varsToAssign.size());
        for(int i = 0; i < varsToAssign.size(); i++) {
            //Change itself, why not?
            //Actually, instead let's try -1, to catch bugs better
            indicesToChangeWhenNull.add(-1);
        }
        //Now we have our functions already selected by the ordering
        //bestOrdering.functionalConjunctIndices;

        //Make AssignmentFunctions out of the ordering
        List<GdlSentence> functionalConjuncts = bestOrdering.getFunctionalConjuncts();
//      System.out.println("functionalConjuncts: " + functionalConjuncts);
        for(int i = 0; i < functionalConjuncts.size(); i++) {
            GdlSentence functionalConjunct = functionalConjuncts.get(i);
            if(functionalConjunct != null) {
                //These are the only ones that could be constant functions
                SentenceForm conjForm = SimpleSentenceForm.create(functionalConjunct);
                FunctionInfo functionInfo = null;

                if(functionInfoMap != null)
                    functionInfo = functionInfoMap.get(conjForm);
                if(functionInfo != null) {
                    //Now we need to figure out which variables are involved
                    //and which are suitable as functional outputs.

                    //1) Which vars are in this conjunct?
                    List<GdlVariable> varsInSentence = GdlUtils.getVariables(functionalConjunct);
                    //2) Of these vars, which is "rightmost"?
                    GdlVariable rightmostVar = getRightmostVar(varsInSentence);
                    //3) Is it only used once in the relation?
                    if(Collections.frequency(varsInSentence, rightmostVar) != 1)
                        continue; //Can't use it
                    //4) Which slot is it used in in the relation?
                    //5) Build an AssignmentFunction if appropriate.
                    //   This should be able to translate from values of
                    //   the other variables to the value of the wanted
                    //   variable.
                    AssignmentFunction function = AssignmentFunction.create((GdlRelation)functionalConjunct, functionInfo, rightmostVar, varsToAssign, headAssignment);
                    //We don't guarantee that this works until we check
                    if(!function.functional())
                        continue;
                    int index = varsToAssign.indexOf(rightmostVar);
                    valuesToCompute.set(index, function);
                    Set<GdlVariable> remainingVarsInSentence = new HashSet<GdlVariable>(varsInSentence);
                    remainingVarsInSentence.remove(rightmostVar);
                    GdlVariable nextRightmostVar = getRightmostVar(remainingVarsInSentence);
                    indicesToChangeWhenNull.set(index, varsToAssign.indexOf(nextRightmostVar));
                }
            }
        }

        //We now have the remainingVars also assigned their domains
        //We also cover the distincts here
        //Assume these are just variables and constants
        distincts = new ArrayList<GdlDistinct>();
        for(GdlLiteral literal : rule.getBody()) {
            if(literal instanceof GdlDistinct)
                distincts.add((GdlDistinct) literal);
        }

        computeVarsToChangePerDistinct();


        //Need to add "distinct" restrictions to head assignment, too...
        checkDistinctsAgainstHead();

        //We are ready for iteration
//      System.out.println("headAssignment: " + headAssignment);
//      System.out.println("varsToAssign: " + varsToAssign);
//      System.out.println("valuesToCompute: " + valuesToCompute);
//      System.out.println("sourceDefiningSlot: " + sourceDefiningSlot);
    }

    private GdlVariable getRightmostVar(Collection<GdlVariable> vars) {
        GdlVariable rightmostVar = null;
        for(GdlVariable var : varsToAssign)
            if(vars.contains(var))
                rightmostVar = var;
        return rightmostVar;
    }

    public AssignmentsImpl() {
        //The assignment is impossible; return nothing
        empty = true;
    }
    @SuppressWarnings("unchecked")
    public AssignmentsImpl(GdlRule rule, /*SentenceModel model,*/ Map<GdlVariable, Set<GdlConstant>> varDomains,
            Map<SentenceForm, ? extends FunctionInfo> functionInfoMap,
            Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) {
        this(Collections.EMPTY_MAP, rule, varDomains, functionInfoMap, completedSentenceFormValues);
    }

    private void checkDistinctsAgainstHead() {
        for(GdlDistinct distinct : distincts) {
            GdlTerm term1 = CommonTransforms.replaceVariables(distinct.getArg1(), headAssignment);
            GdlTerm term2 = CommonTransforms.replaceVariables(distinct.getArg2(), headAssignment);
            if(term1.equals(term2)) {
                //This fails
                empty = true;
                allDone = true;
            }
        }
    }
    @Override
    public Iterator<Map<GdlVariable, GdlConstant>> iterator() {
        return new AssignmentIteratorImpl(getPlan());
    }
    @Override
    public AssignmentIterator getIterator() {
        return new AssignmentIteratorImpl(getPlan());
    }

    private AssignmentIterationPlan getPlan() {
        return AssignmentIterationPlan.create(varsToAssign,
                tuplesBySource,
                headAssignment,
                indicesToChangeWhenNull,
                distincts,
                varsToChangePerDistinct,
                valuesToCompute,
                sourceDefiningSlot,
                valuesToIterate,
                varsChosenBySource,
                putDontCheckBySource,
                empty,
                allDone);
    }

    private void computeVarsToChangePerDistinct() {
        //remember that iterators must be set up first
        varsToChangePerDistinct = new ArrayList<GdlVariable>(varsToAssign.size());
        for(GdlDistinct distinct : distincts) {
            //For two vars, we want to record the later of the two
            //For one var, we want to record the one
            //For no vars, we just put null
            List<GdlVariable> varsInDistinct = new ArrayList<GdlVariable>(2);
            if(distinct.getArg1() instanceof GdlVariable)
                varsInDistinct.add((GdlVariable) distinct.getArg1());
            if(distinct.getArg2() instanceof GdlVariable)
                varsInDistinct.add((GdlVariable) distinct.getArg2());

            GdlVariable varToChange = null;
            if(varsInDistinct.size() == 1) {
                varToChange = varsInDistinct.get(0);
            } else if(varsInDistinct.size() == 2) {
                varToChange = getRightmostVar(varsInDistinct);
            }
            varsToChangePerDistinct.add(varToChange);
        }
    }

    public static Assignments getAssignmentsProducingSentence(
            GdlRule rule, GdlSentence sentence, /*SentenceModel model,*/ Map<GdlVariable, Set<GdlConstant>> varDomains,
            Map<SentenceForm, FunctionInfo> functionInfoMap,
            Map<SentenceForm, ? extends Collection<GdlSentence>> completedSentenceFormValues) {
        //First, we see which variables must be set according to the rule head
        //(and see if there's any contradiction)
        Map<GdlVariable, GdlConstant> headAssignment = new HashMap<GdlVariable, GdlConstant>();
        if(!setVariablesInHead(rule.getHead(), sentence, headAssignment)) {
            return new AssignmentsImpl();//Collections.emptySet();
        }
        //Then we come up with all the assignments of the rest of the variables

        //We need to look for functions we can make use of

        return new AssignmentsImpl(headAssignment, rule, varDomains, functionInfoMap, completedSentenceFormValues);
    }

    //returns true if all variables were set successfully
    private static boolean setVariablesInHead(GdlSentence head,
            GdlSentence sentence, Map<GdlVariable, GdlConstant> assignment) {
        if(head instanceof GdlProposition)
            return true;
        return setVariablesInHead(head.getBody(), sentence.getBody(), assignment);
    }
    private static boolean setVariablesInHead(List<GdlTerm> head,
            List<GdlTerm> sentence, Map<GdlVariable, GdlConstant> assignment) {
        for(int i = 0; i < head.size(); i++) {
            GdlTerm headTerm = head.get(i);
            GdlTerm refTerm = sentence.get(i);
            if(headTerm instanceof GdlConstant) {
                if(!refTerm.equals(headTerm))
                    //The rule can't produce this sentence
                    return false;
            } else if(headTerm instanceof GdlVariable) {
                GdlVariable var = (GdlVariable) headTerm;
                GdlConstant curValue = assignment.get(var);
                if(curValue != null && !curValue.equals(refTerm)) {
                    //inconsistent assignment (e.g. head is (rel ?x ?x), sentence is (rel 1 2))
                    return false;
                }
                assignment.put(var, (GdlConstant)refTerm);
            } else if(headTerm instanceof GdlFunction) {
                //Recurse on the body
                GdlFunction headFunction = (GdlFunction) headTerm;
                GdlFunction refFunction = (GdlFunction) refTerm;
                if(!setVariablesInHead(headFunction.getBody(), refFunction.getBody(), assignment))
                    return false;
            }
        }
        return true;
    }


    /**
     * Finds the iteration order (including variables, functions, and
     * source conjuncts) that is expected to result in the fastest iteration.
     *
     * The value that is compared for each ordering is the product of:
     * - For each source conjunct, the number of tuples offered by the conjunct;
     * - For each variable not defined by a function, the size of its domain.
     *
     * @param functionInfoMap
     * @param completedSentenceFormSizes For each sentence form, this may optionally
     * contain the number of possible sentences of this form. This is useful if the
     * number of sentences is much lower than the product of its variables' domain
     * sizes; however, if this contains sentence forms where the set of sentences
     * is unknown, then it may return an ordering that is unusable.
     */
    protected static IterationOrderCandidate getBestIterationOrderCandidate(GdlRule rule,
            /*SentenceModel model,*/
            Map<GdlVariable, Set<GdlConstant>> varDomains,
            Map<SentenceForm, ? extends FunctionInfo> functionInfoMap,
            Map<SentenceForm, Integer> completedSentenceFormSizes,
            Map<GdlVariable, GdlConstant> preassignment,
            boolean analyticFunctionOrdering) {
        //Here are the things we need to pass into the first IOC constructor
        List<GdlSentence> sourceConjunctCandidates = new ArrayList<GdlSentence>();
        //What is a source conjunct candidate?
        //- It is a positive conjunct in the rule (i.e. a GdlSentence in the body).
        //- It has already been fully defined; i.e. it is not recursively defined in terms of the current form.
        //Furthermore, we know the number of potentially true tuples in it.
        List<GdlVariable> varsToAssign = GdlUtils.getVariables(rule);
        List<GdlVariable> newVarsToAssign = new ArrayList<GdlVariable>();
        for(GdlVariable var : varsToAssign)
            if(!newVarsToAssign.contains(var))
                newVarsToAssign.add(var);
        varsToAssign = newVarsToAssign;
        if(preassignment != null)
            varsToAssign.removeAll(preassignment.keySet());

        //Calculate var domain sizes
        Map<GdlVariable, Integer> varDomainSizes = getVarDomainSizes(varDomains/*rule, model*/);

        List<Integer> sourceConjunctSizes = new ArrayList<Integer>();
        for(GdlLiteral conjunct : rule.getBody()) {
            if(conjunct instanceof GdlRelation) {
                SentenceForm form = SimpleSentenceForm.create((GdlRelation)conjunct);
                if(completedSentenceFormSizes != null
                        && completedSentenceFormSizes.containsKey(form)) {
                    int size = completedSentenceFormSizes.get(form);
                    //New: Don't add if it will be useless as a source
                    //For now, we take a strict definition of that
                    //Compare its size with the product of the domains
                    //of the variables it defines
                    //In the future, we could require a certain ratio
                    //to decide that this is worthwhile
                    GdlRelation relation = (GdlRelation) conjunct;
                    int maxSize = 1;
                    Set<GdlVariable> vars = new HashSet<GdlVariable>(GdlUtils.getVariables(relation));
                    for(GdlVariable var : vars) {
                        int domainSize = varDomainSizes.get(var);
                        maxSize *= domainSize;
                    }
                    if(size >= maxSize)
                        continue;
                    sourceConjunctCandidates.add(relation);
                    sourceConjunctSizes.add(size);
                }
            }
        }

        List<GdlSentence> functionalSentences = new ArrayList<GdlSentence>();
        List<FunctionInfo> functionalSentencesInfo = new ArrayList<FunctionInfo>();
        for(GdlLiteral conjunct : rule.getBody()) {
            if(conjunct instanceof GdlSentence) {
                SentenceForm form = SimpleSentenceForm.create((GdlSentence) conjunct);
                if(functionInfoMap != null && functionInfoMap.containsKey(form)) {
                    functionalSentences.add((GdlSentence) conjunct);
                    functionalSentencesInfo.add(functionInfoMap.get(form));
                }
            }
        }

        //TODO: If we have a head assignment, treat everything as already replaced
        //Maybe just translate the rule? Or should we keep the pool clean?

        IterationOrderCandidate emptyCandidate = new IterationOrderCandidate(varsToAssign, sourceConjunctCandidates,
                sourceConjunctSizes, functionalSentences, functionalSentencesInfo, varDomainSizes);
        PriorityQueue<IterationOrderCandidate> searchQueue = new PriorityQueue<IterationOrderCandidate>();
        searchQueue.add(emptyCandidate);

        while(!searchQueue.isEmpty()) {
            IterationOrderCandidate curNode = searchQueue.remove();
//          System.out.println("Node being checked out: " + curNode);
            if(curNode.isComplete()) {
                //This is the complete ordering with the lowest heuristic value
                return curNode;
            }
            searchQueue.addAll(curNode.getChildren(analyticFunctionOrdering));
        }
        throw new RuntimeException("Found no complete iteration orderings");
    }

    private static Map<GdlVariable, Integer> getVarDomainSizes(/*GdlRule rule,
            SentenceModel model*/Map<GdlVariable, Set<GdlConstant>> varDomains) {
        Map<GdlVariable, Integer> varDomainSizes = new HashMap<GdlVariable, Integer>();
        //Map<GdlVariable, Set<GdlConstant>> varDomains = model.getVarDomains(rule);
        for(GdlVariable var : varDomains.keySet()) {
            varDomainSizes.put(var, varDomains.get(var).size());
        }
        return varDomainSizes;
    }

    public static long getNumAssignmentsEstimate(GdlRule rule, Map<GdlVariable, Set<GdlConstant>> varDomains, ConstantChecker checker) throws InterruptedException {
        //First we need the best iteration order
        //Arguments we'll need to pass in:
        //- A SentenceModel
        //- constant forms
        //- completed sentence form sizes
        //- Variable domain sizes?

        Map<SentenceForm, FunctionInfo> functionInfoMap = new HashMap<SentenceForm, FunctionInfo>();
        for (SentenceForm form : checker.getConstantSentenceForms()) {
            functionInfoMap.put(form, FunctionInfoImpl.create(form, checker));
        }

        //Populate variable domain sizes using the constant checker
        Map<SentenceForm, Integer> domainSizes = new HashMap<SentenceForm, Integer>();
        for(SentenceForm form : checker.getConstantSentenceForms()) {
            domainSizes.put(form, checker.getTrueSentences(form).size());
        }
        //TODO: Propagate these domain sizes as estimates for other rules?
        //Look for literals in the body of the rule and their ancestors?
        //Could we possibly do this elsewhere?

        IterationOrderCandidate ordering = getBestIterationOrderCandidate(rule, /*model,*/varDomains, functionInfoMap, null, null, true);
        return ordering.getHeuristicValue();
    }
}
