package accrue.analysis.interprocvarcontext;

import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import polyglot.types.ClassType;
import polyglot.util.InternalCompilerError;
import accrue.analysis.interprocanalysis.AbstractLocation;
import accrue.analysis.interprocanalysis.ExitMap;

/**
 * The analysis context produced by analyzing a command. Is a map from 
 * the possible ways to exit the command to PolContexts, and also a Policy
 * for the result of an expression.
 * 
 * Note that the exception types are exact: if a VarContext<T> is associated with an
 * exception type, then that is the suitable context when an exception of EXACTLY that
 * type is thrown.
 * 
 * This is a functional implementation: a FinalVarContext is immutable.
 */
public class FinalVarContext<T extends AbstractValue<T>> extends
        ExitMap<VarContext<T>> {

    /**
     * Create one from its parts
     * 
     * @param m
     *            map from exit key to variable context
     * 
     * @param exTypes
     *            types of exceptions that can be thrown
     */
    protected FinalVarContext(Map<Key, VarContext<T>> m, Set<ClassType> exTypes) {
        super(m, exTypes);
    }

    /**
     * Returns a copy so that it is ok to modify the map.
     */
    @Override
    protected FinalVarContext<T> copy() {
        return new FinalVarContext<T>(new LinkedHashMap<Key, VarContext<T>>(this.map),
                                      new LinkedHashSet<ClassType>(this.exceptionTypes));
    }

    @Override
    protected FinalVarContext<T> create(Map<Key, VarContext<T>> m,
            Set<ClassType> exTypes) {
        return new FinalVarContext<T>(m, exTypes);
    }

    /**
     * Modify all locations not in the keep set
     * 
     * @param keepLocs
     *            set of locations to keep from <code>this</code>
     * @param addLocs
     *            locations to add/replace in the context
     * @return new context with the locations replaced/added
     */
    FinalVarContext<T> modifyAllLocationDomains(Set<AbstractLocation> keepLocs,
            Map<AbstractLocation, T> addLocs) {
        FinalVarContext<T> copy = this.copy();
        copy.map.clear();
        for (Key k : this.map.keySet()) {
            VarContext<T> c = this.map.get(k);
            if (c == null) {
                throw new InternalCompilerError("What the? key is " + k
                        + " map is " + this.map);
            }

            LinkedHashMap<AbstractLocation, T> locs =
                    new LinkedHashMap<AbstractLocation, T>(c.locations());
            // drop everything except in the keep set
            locs.keySet().retainAll(keepLocs);

            // now add in all the other locations
            locs.putAll(addLocs);

            c = c.locations(locs);

            copy.map.put(k, c);
        }
        return copy;
    }

    /**
     * Restore elements of the input context into the contexts in <code>this</code>. 
     * This should be called after a procedure call is processed to restore the 
     * appropriate parts of the caller's context.
     * <p>
     * Restores the suspended execution stack.
     * <p>
     * If <code>restoreLocalsAndStack</code> is true then this method also restores the local variables.
     * And restores execution stack into the normal termination context (if there is one).
     * <p>
     * This also serves as an extension point for subclasses to allow them to restore
     * other parts of a context. 
     * 
     * @param toRestore 
     *                  context used to get the elements to restore
     * @param restoreLocalsAndStack 
     *                  true if we want to restore local variables and the expression
     *                  stack (the expression stack is only restored in the normal 
     *                  termination context)
     *                  
     * @return a new {@link FinalVarContext} with certain elements restored from the 
     *          given context
     */
    public FinalVarContext<T> restoreContext(VarContext<T> toRestore,
            boolean restoreLocalsAndStack) {

        FinalVarContext<T> copy = this.copy();
        copy.map.clear();
        for (Key k : this.map.keySet()) {
            VarContext<T> c = this.map.get(k);
            if (c == null) {
                throw new InternalCompilerError("What the? key is " + k
                        + " map is " + this.map);
            }
            c = c.restoreCallerContext(toRestore, restoreLocalsAndStack, k);
            copy.map.put(k, c);
        }
        return copy;
    }

    @Override
    public FinalVarContext<T> addToAll(VarContext<T> c) {
        Map<Key, VarContext<T>> newMap =
                new LinkedHashMap<Key, VarContext<T>>(this.map);
        boolean changed = false;
        for (Key k : this.map.keySet()) {
            VarContext<T> origContext = this.map.get(k);
            VarContext<T> newContext = VarContext.upperBound(origContext, c);
            newMap.put(k, newContext);
            changed = changed || newContext != origContext;
        }

        if (!changed) return this;
        return create(newMap, this.exceptionTypes);
    }

    @Override
    public String toString() {
        return "FinalVarContext [map=" + map + "]";
    }

}
