package accrue.analysis.antidep;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import polyglot.ast.ArrayAccess;
import polyglot.ast.Cast;
import polyglot.ast.CodeNode;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.New;
import polyglot.ast.ProcedureDecl;
import polyglot.types.MethodInstance;
import polyglot.types.ProcedureInstance;
import polyglot.types.SemanticException;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.visit.FlowGraph.Peer;
import accrue.AccrueExtensionInfo;
import accrue.analysis.defuse.DefUseAnalysisFactory;
import accrue.analysis.ext.AccrueExt;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtCast;
import accrue.analysis.ext.ExtExpr;
import accrue.analysis.interprocanalysis.AbstractLocation;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisUnit;
import accrue.analysis.interprocanalysis.DataFlowNodeIdentifier;
import accrue.analysis.interprocanalysis.EdgeIdentifier;
import accrue.analysis.interprocanalysis.EdgeIdentifier.HeapUseKey;
import accrue.analysis.interprocanalysis.EdgeIdentifier_c;
import accrue.analysis.interprocanalysis.ExitMap.Key;
import accrue.analysis.interprocanalysis.WorkQueue;
import accrue.analysis.interprocvarcontext.AnalysisUtilVarContextDataFlow;
import accrue.analysis.interprocvarcontext.FinalVarContext;
import accrue.analysis.interprocvarcontext.VarContext;
import accrue.analysis.pointer.HContext;

public class AntiDepAnalysisUtil extends
        AnalysisUtilVarContextDataFlow<VarContext<AntiDepAbsVal>, AntiDepAbsVal> {

    protected TypeSystem ts;
    private final FinalVarContext<AntiDepAbsVal> BOTTOM;

    public AntiDepAnalysisUtil(WorkQueue<VarContext<AntiDepAbsVal>> wq,
            AnalysisUnit currentAnalysisUnit, AccrueExtensionInfo extInfo) {
        super(wq, currentAnalysisUnit, extInfo);
        this.ts = extInfo.typeSystem();
        this.BOTTOM = createFinalVarContext(createInitContext());
    }

    @Override
    public FinalVarContext<AntiDepAbsVal> analyze(CodeNode n,
            VarContext<AntiDepAbsVal> input) throws SemanticException {
        AntiDepDataFlow d =
                new AntiDepDataFlow(this,
                                    this.workQueue(),
                                    this.extInfo.scheduler().currentJob(),
                                    this.extInfo.typeSystem(),
                                    this.extInfo.nodeFactory());
        d.dataflow(n, createInitContext());
        return BOTTOM;
    }

    @Override
    public VarContext<AntiDepAbsVal> createInitContext() {
        return new VarContext<AntiDepAbsVal>(AntiDepAnalysisFactory.ANALYSIS_NAME,
                                             AntiDepAbsVal.EMPTY);
    }

    private void analyzeCall(AnalysisContext calleeContext, ProcedureInstance pi) {
        // get the implementing method body to add the argument names...
        ProcedureDecl pd = (ProcedureDecl) workQueue.registrar().getCode(pi);
        AnalysisUnit callee = null;

        if (pd != null && pd.body() != null) {
            callee = new AnalysisUnit(pd, calleeContext);

            try {
                this.workQueue.getAnalysisResult(callee,
                                                 createInitContext(),
                                                 this);
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Unexpected semantic exception",
                                                e);
            }
        }
    }

    @Override
    public FinalVarContext<AntiDepAbsVal> callNonVirtual(ProcedureInstance pi,
            AnalysisContext calleeContext, HContext receiver,
            AntiDepAbsVal receiverAbsVal, VarContext<AntiDepAbsVal> input,
            DataFlowNodeIdentifier callSite, boolean canTerminateNormally)
            throws SemanticException {
        // Schedule call targets 
        analyzeCall(calleeContext, pi);

        // pop arguments
        int numArgs = pi.formalTypes().size();
        if (pi instanceof MethodInstance || callSite.getNode() instanceof New)
            numArgs++;
        input = input.popExprResults(numArgs);

        AccrueExt ext = AccrueExt_c.ext(callSite.getNode());
        Set<AbstractLocation> obs =
                ext.getAnalysisResult(DefUseAnalysisFactory.OBSERVED_LOCATIONS,
                                      currentContext());

        if (pi instanceof MethodInstance) {
            MethodInstance mi = (MethodInstance) pi;
            if (!mi.returnType().isVoid()) {
                input =
                        input.pushExprResult(bottomAbstractValue(), "returnX2-"
                                + pi);
            }
        }
        if (obs == null || obs.isEmpty()) {
            return createFinalVarContext(input);
        }

        HeapUseKey key = new HeapUseKey(obs, true);
        Set<EdgeIdentifier> nodeID =
                Collections.<EdgeIdentifier> singleton(new EdgeIdentifier_c(key,
                                                                            callSite));
        AntiDepAbsVal uses = new AntiDepAbsVal(nodeID);
        input = addLocations(input, obs, uses);

        return createFinalVarContext(input);
    }

    protected boolean mayBeNull(AntiDepAbsVal exprDFI, Expr e,
            Peer<VarContext<AntiDepAbsVal>> peer) {
        ExtExpr extExpr = (ExtExpr) AccrueExt_c.ext(e);
        return extExpr.mayBeNull(currentContext(), peer.peerKey());

    }

    protected boolean mayThrowClassCastException(AntiDepAbsVal exprDFI,
            Cast cast, Peer<VarContext<AntiDepAbsVal>> peer) {
        ExtCast ext = (ExtCast) AccrueExt_c.ext(cast);
        return ext.mayThrowClassCastException(currentContext());
    }

    protected Set<AbstractLocation> abstractLocations(AntiDepAbsVal targetDFI,
            Field f) {
        return abstractLocations(f);
    }

    protected Set<AbstractLocation> abstractLocations(AntiDepAbsVal arrayDFI,
            ArrayAccess aa) {
        return abstractLocations(aa);
    }

    protected Set<HContext> pointsTo(AntiDepAbsVal exprDFI, Expr e) {
        return pointsTo(e);
    }

    @Override
    public FinalVarContext<AntiDepAbsVal> analyzeInitializersForDataFlow(
            VarContext<AntiDepAbsVal> before) throws SemanticException {
        FinalVarContext<AntiDepAbsVal> after =
                (FinalVarContext<AntiDepAbsVal>) analyzeInitializers(before);
        return after;
    }

    @Override
    protected FinalVarContext<AntiDepAbsVal> getAnalysisResultsFromWorkQueue(
            AnalysisUnit callee, VarContext<AntiDepAbsVal> beforeCall,
            DataFlowNodeIdentifier callSite) throws SemanticException {
        return (FinalVarContext<AntiDepAbsVal>) workQueue.getAnalysisResult(callee,
                                                                            beforeCall,
                                                                            this);
    }

    @Override
    public AntiDepAbsVal bottomAbstractValue() {
        return AntiDepAbsVal.EMPTY;
    }

    @Override
    protected FinalVarContext<AntiDepAbsVal> guessAnalysisForMissingReceiver(
            ProcedureInstance pi, VarContext<AntiDepAbsVal> input,
            AntiDepAbsVal receiverAbsVal, List<AntiDepAbsVal> argAbsVals,
            Set<Key> expectedExits, DataFlowNodeIdentifier callSiteNode) {
        AntiDepAbsVal ret = this.bottomAbstractValue();
        VarContext<AntiDepAbsVal> nt = input.returnResult(ret);
        VarContext<AntiDepAbsVal> ex =
                input.clearExprResultStack().returnResult(ret);
        return this.makeFinalVarContext(expectedExits, nt, ex);
    }

    protected static List<String> List(String s1) {
        return Collections.singletonList(s1);
    }
}
