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

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

import org.ggp.base.util.concurrency.ConcurrencyUtils;
import org.ggp.base.util.gdl.GdlUtils;
import org.ggp.base.util.gdl.grammar.GdlConstant;
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.transforms.ConstantChecker;

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

//Represents information about a sentence form that is constant.
public class FunctionInfoImpl implements FunctionInfo {
    private SentenceForm form;

    //True iff the slot has at most one value given the other slots' values
    private List<Boolean> dependentSlots = new ArrayList<Boolean>();
    private List<Map<ImmutableList<GdlConstant>, GdlConstant>> valueMaps = Lists.newArrayList();

    public FunctionInfoImpl(SentenceForm form, Set<GdlSentence> trueSentences) throws InterruptedException {
        this.form = form;

        int numSlots = form.getTupleSize();

        for(int i = 0; i < numSlots; i++) {
            //We want to establish whether or not this is a constant...
            Map<ImmutableList<GdlConstant>, GdlConstant> functionMap = Maps.newHashMap();
            boolean functional = true;
            for (GdlSentence sentence : trueSentences) {
                ConcurrencyUtils.checkForInterruption();
                List<GdlConstant> tuple = GdlUtils.getTupleFromGroundSentence(sentence);
                List<GdlConstant> tuplePart = Lists.newArrayListWithCapacity(tuple.size() - 1);
                tuplePart.addAll(tuple.subList(0, i));
                tuplePart.addAll(tuple.subList(i + 1, tuple.size()));
                if(functionMap.containsKey(tuplePart)) {
                    //We have two tuples with different values in just this slot
                    functional = false;
                    break;
                }
                //Otherwise, we record it
                functionMap.put(ImmutableList.copyOf(tuplePart), tuple.get(i));
            }

            if(functional) {
                //Record the function
                dependentSlots.add(true);
                valueMaps.add(functionMap);
            } else {
                //Forget it
                dependentSlots.add(false);
                valueMaps.add(null);
            }
        }
    }


    @Override
    public Map<ImmutableList<GdlConstant>, GdlConstant> getValueMap(int index) {
        return valueMaps.get(index);
    }

    @Override
    public List<Boolean> getDependentSlots() {
        return dependentSlots;
    }

    /**
     * Given a sentence of the constant form's sentence form, finds all
     * the variables in the sentence that can be produced functionally.
     *
     * Note the corner case: If a variable appears twice in a sentence,
     * it CANNOT be produced in this way.
     */
    @Override
    public Set<GdlVariable> getProducibleVars(GdlSentence sentence) {
        if(!form.matches(sentence))
            throw new RuntimeException("Sentence "+sentence+" does not match constant form");
        List<GdlTerm> tuple = GdlUtils.getTupleFromSentence(sentence);

        Set<GdlVariable> candidateVars = new HashSet<GdlVariable>();
        //Variables that appear multiple times go into multipleVars
        Set<GdlVariable> multipleVars = new HashSet<GdlVariable>();
        //...which, of course, means we have to spot non-candidate vars
        Set<GdlVariable> nonCandidateVars = new HashSet<GdlVariable>();

        for(int i = 0; i < tuple.size(); i++) {
            GdlTerm term = tuple.get(i);
            if(term instanceof GdlVariable
                    && !multipleVars.contains(term)) {
                GdlVariable var = (GdlVariable) term;
                if(candidateVars.contains(var)
                        || nonCandidateVars.contains(var)) {
                    multipleVars.add(var);
                    candidateVars.remove(var);
                } else if(dependentSlots.get(i)) {
                    candidateVars.add(var);
                } else {
                    nonCandidateVars.add(var);
                }
            }
        }

        return candidateVars;

    }
    public static FunctionInfo create(SentenceForm form,
            ConstantChecker constantChecker) throws InterruptedException {
        return new FunctionInfoImpl(form, ImmutableSet.copyOf(constantChecker.getTrueSentences(form)));
    }
    public static FunctionInfo create(SentenceForm form,
            Set<GdlSentence> set) throws InterruptedException {
        return new FunctionInfoImpl(form, set);
    }
    @Override
    public SentenceForm getSentenceForm() {
        return form;
    }
    @Override
    public String toString() {
        return "FunctionInfoImpl [form=" + form + ", dependentSlots="
                + dependentSlots + ", valueMaps=" + valueMaps + "]";
    }
}