package accrue.analysis.notnulldataflow;

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

import polyglot.ast.CodeNode;
import polyglot.ast.Node;
import polyglot.main.Report;
import polyglot.types.MethodInstance;
import polyglot.types.ProcedureInstance;
import polyglot.types.SemanticException;
import accrue.AccrueExtensionInfo;
import accrue.analysis.AnalysisTopics;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisUnit;
import accrue.analysis.interprocanalysis.DataFlowNodeIdentifier;
import accrue.analysis.interprocanalysis.ExitMap;
import accrue.analysis.interprocanalysis.ExitMap.ExceptionKey;
import accrue.analysis.interprocanalysis.ExitMap.Key;
import accrue.analysis.interprocanalysis.WorkQueue;
import accrue.analysis.interprocvarcontext.AnalysisUtilVarContextDataFlow;
import accrue.analysis.interprocvarcontext.AnalysisUtilVarContext;
import accrue.analysis.interprocvarcontext.FinalVarContext;
import accrue.analysis.interprocvarcontext.LibrarySignature;
import accrue.analysis.interprocvarcontext.VarContext;
import accrue.analysis.pointer.HContext;

public class NotNullAnalysisUtil extends
        AnalysisUtilVarContextDataFlow<VarContext<NotNullAbsVal>, NotNullAbsVal> {
    public NotNullAnalysisUtil(WorkQueue<VarContext<NotNullAbsVal>> workQueue,
            AnalysisUnit currentProcCall, AccrueExtensionInfo extInfo) {
        super(workQueue, currentProcCall, extInfo);
    }

    @Override
    public FinalVarContext<NotNullAbsVal> analyze(CodeNode n,
            VarContext<NotNullAbsVal> before) throws SemanticException {
        NotNullDataFlow c =
                new NotNullDataFlow(this,
                                    this.workQueue(),
                                    this.extInfo.scheduler().currentJob(),
                                    this.extInfo.typeSystem(),
                                    this.extInfo.nodeFactory());

        return c.dataflow(n, before);
    }

    public static final LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal> NOOP_SIG =
            new LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal>() {
                @Override
                public FinalVarContext<NotNullAbsVal> process(
                        ProcedureInstance pi,
                        AnalysisContext calleeContext,
                        HContext receiver,
                        VarContext<NotNullAbsVal> input,
                        NotNullAbsVal receiverAbsVal,
                        List<NotNullAbsVal> argAbsVals,
                        AnalysisUtilVarContext<VarContext<NotNullAbsVal>, NotNullAbsVal> autil,
                        Set<ExitMap.Key> expectedExits, Node node) {
                    if (pi instanceof MethodInstance) {
                        MethodInstance mi = (MethodInstance) pi;
                        if (!mi.returnType().isVoid()) {
                            if (mi.returnType().isReference()) {
                                input =
                                        input.pushExprResult(NotNullAbsVal.MAYBE_NULL,
                                                             "not null signature for "
                                                                     + pi);
                            }
                            else {
                                input =
                                        input.pushExprResult(NotNullAbsVal.NOT_NULL,
                                                             "not null signature for "
                                                                     + pi);
                            }
                        }
                    }
                    NotNullAnalysisUtil nautil = (NotNullAnalysisUtil) autil;
                    FinalVarContext<NotNullAbsVal> ret =
                            nautil.createFinalVarContext(input);
                    if (!expectedExits.contains(ExitMap.KEY_NORM_TERM)) {
                        ret =
                                (FinalVarContext<NotNullAbsVal>) ret.removeNormalTermination();
                    }
                    for (ExitMap.Key k : expectedExits) {
                        if (ExitMap.KEY_NORM_TERM.equals(k)) {
                            // already in there
                            continue;
                        }
                        if (k instanceof ExceptionKey) {
                            ret =
                                    (FinalVarContext<NotNullAbsVal>) ret.addKey(k,
                                                                                input.returnResult(NotNullAbsVal.NOT_NULL)
                                                                                     .clearExprResultStack());
                        }
                        else {
                            ret =
                                    (FinalVarContext<NotNullAbsVal>) ret.addKey(k,
                                                                                input);
                        }
                    }
                    return ret;
                }
            };
    public static final LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal> NOOP_NONNULL_RESULT_SIG =
            new LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal>() {
                @Override
                public FinalVarContext<NotNullAbsVal> process(
                        ProcedureInstance pi,
                        AnalysisContext calleeContext,
                        HContext receiver,
                        VarContext<NotNullAbsVal> input,
                        NotNullAbsVal receiverAbsVal,
                        List<NotNullAbsVal> argAbsVals,
                        AnalysisUtilVarContext<VarContext<NotNullAbsVal>, NotNullAbsVal> autil,
                        Set<ExitMap.Key> expectedExits, Node node) {
                    boolean hasReturn =
                            pi instanceof MethodInstance
                                    && !((MethodInstance) pi).returnType()
                                                             .isVoid();
                    VarContext<NotNullAbsVal> normal =
                            hasReturn ? input.pushExprResult(NotNullAbsVal.NOT_NULL,
                                                             "libSig for " + pi)
                                    : input;
                    return makeFinalVarContext(expectedExits,
                                               normal,
                                               input.returnResult(NotNullAbsVal.NOT_NULL)
                                                    .clearExprResultStack(),
                                               autil);
                }
            };
    public static final LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal> NOOP_MAYBENULL_RESULT_SIG =
            new LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal>() {
                @Override
                public FinalVarContext<NotNullAbsVal> process(
                        ProcedureInstance pi,
                        AnalysisContext calleeContext,
                        HContext receiver,
                        VarContext<NotNullAbsVal> input,
                        NotNullAbsVal receiverAbsVal,
                        List<NotNullAbsVal> argAbsVals,
                        AnalysisUtilVarContext<VarContext<NotNullAbsVal>, NotNullAbsVal> autil,
                        Set<ExitMap.Key> expectedExits, Node node) {
                    boolean hasReturn =
                            pi instanceof MethodInstance
                                    && !((MethodInstance) pi).returnType()
                                                             .isVoid();
                    VarContext<NotNullAbsVal> normal =
                            hasReturn ? input.pushExprResult(NotNullAbsVal.MAYBE_NULL,
                                                             "libSig for " + pi)
                                    : input;

                    return makeFinalVarContext(expectedExits,
                                               normal,
                                               input.returnResult(NotNullAbsVal.NOT_NULL)
                                                    .clearExprResultStack(),
                                               autil);
                }
            };

    @Override
    public FinalVarContext<NotNullAbsVal> analyzeInitializersForDataFlow(
            VarContext<NotNullAbsVal> before) throws SemanticException {
        return (FinalVarContext<NotNullAbsVal>) this.analyzeInitializers(before);
    }

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

    @Override
    public NotNullAbsVal bottomAbstractValue() {
        return NotNullAbsVal.NOT_NULL;
    }

    @Override
    protected FinalVarContext<NotNullAbsVal> guessAnalysisForMissingCode(
            ProcedureInstance pi, AnalysisContext calleeContext,
            HContext receiver, VarContext<NotNullAbsVal> input,
            NotNullAbsVal receiverAbsVal, List<NotNullAbsVal> argAbsVals,
            Set<ExitMap.Key> expectedExits, Node node) {
        if (Report.should_report(AnalysisTopics.NULL_ANALYSIS, 3)) {
            Report.report(3, "  ++++++ guessing NotNullAnalysis analysis for "
                    + pi);
        }
        LibrarySignature<VarContext<NotNullAbsVal>, NotNullAbsVal> sig =
                NOOP_SIG;
        if (pi instanceof MethodInstance
                && !((MethodInstance) pi).returnType().isVoid()) {
            sig = NOOP_MAYBENULL_RESULT_SIG;
        }
        return sig.process(pi,
                           calleeContext,
                           receiver,
                           input,
                           receiverAbsVal,
                           argAbsVals,
                           this,
                           expectedExits,
                           node);
    }

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

}
