package accrue.analysis.ext;

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

import polyglot.ast.ArrayInit;
import polyglot.ast.Expr;
import polyglot.ast.Lit;
import polyglot.ast.New;
import polyglot.ast.NewArray;
import polyglot.ast.Special;
import polyglot.util.InternalCompilerError;
import polyglot.util.SerialVersionUID;
import polyglot.visit.FlowGraph.PeerKey;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisContextMap;
import accrue.analysis.interprocvarcontext.AnalysisContextPeerMap;
import accrue.analysis.pointer.ConstructorContext;
import accrue.analysis.pointer.ReferenceVariable;
import accrue.analysis.pointer.RegisterPointerStmtsVisitor;
import accrue.analysis.pointer.StmtRegistrar;

public class ExtExpr extends ExtTerm {
    private static final long serialVersionUID = SerialVersionUID.generate();

    private Map<ConstructorContext, ReferenceVariable> referenceVariables;
    private AnalysisContextPeerMap<Boolean> maybeNullResults =
            new AnalysisContextPeerMap<Boolean>();
    private AnalysisContextMap<Boolean> maybeNullResultsNoPeer =
            new AnalysisContextMap<Boolean>();
    private boolean nullResultsNoPeerValid = false;

    public void setReferenceVariable(ReferenceVariable node,
            ConstructorContext constructorContext) {
        if (this.referenceVariables == null) {
            referenceVariables =
                    new LinkedHashMap<ConstructorContext, ReferenceVariable>();
        }
        if (referenceVariables.containsKey(constructorContext)) {
            throw new InternalCompilerError("Nodes should only be set once for each constructorContext!");
        }
        this.referenceVariables.put(constructorContext, node);
    }

    public ReferenceVariable getReferenceVariable(
            ConstructorContext constructorContext) {
        if (this.referenceVariables == null) {
            return null;
        }
        return referenceVariables.get(constructorContext);
    }

    public Set<ConstructorContext> getConstructorContexts() {
        return this.referenceVariables.keySet();
    }

    /**
     * This method needs to ensure that: 1. For all expressions of reference
     * type, there is a local node 2. That local node has been assigned to
     * appropriately, based on the semantics of the expression 3. Exceptions
     * have been assigned to the appropriate handlers (including exceptions that
     * may be generated by this expression).
     */
    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {
        throw new InternalCompilerError("Do not yet support pointer analysis for "
                + this.node().getClass().getSimpleName());
    }

    @Override
    public int numArgumentsOnResultStack() {
        throw new InternalCompilerError("Don't know how many arguments on stack for "
                + this.node + " (" + this.node.getClass().getName() + ")");

    }

    public boolean mayBeNull(AnalysisContext context, PeerKey peerKey) {
        Expr e = (Expr) this.node();
        if (neverNull(e)) {
            return false;
        }

        if (context == null || peerKey == null) return true;

        Boolean b = maybeNullResults.get(context, peerKey);
        if (b == null) return true;
        return b.booleanValue();
    }

    private static boolean neverNull(Expr e) {
        if (e.type().isPrimitive()) return true;
        if (e instanceof Special) return true;
        if (e instanceof New) return true;
        if (e instanceof NewArray) return true;
        if (e instanceof ArrayInit) return true;
        if (e instanceof Lit) {
            Lit l = (Lit) e;
            if (l.constantValue() != null) return true;
        }
        return false;
    }

    public boolean mayBeNull(AnalysisContext context) {
        Expr e = (Expr) this.node();
        if (neverNull(e)) {
            return false;
        }

        if (context == null) return true;

        if (nullResultsNoPeerValid) {
            Boolean b = maybeNullResultsNoPeer.get(context);
            if (b == null) return true;
            return b.booleanValue();
        }
        return true;
    }

    public void recordNullAnalysisResult(AnalysisContext context,
            PeerKey peerKey, boolean maybeNull) {
        maybeNullResults.put(context, peerKey, Boolean.valueOf(maybeNull));
        Boolean b = maybeNullResultsNoPeer.get(context);
        boolean c = maybeNull;
        if (b != null) c = c || b;
        if (b == null || b.booleanValue() != c) {
            maybeNullResultsNoPeer.put(context, c);
        }
    }

    public void setNullResultsNoPeerValid() {
        this.nullResultsNoPeerValid = true;
    }
}
