package accrue.analysis.ctldep;

import java.util.Set;

import polyglot.ast.Call;
import polyglot.ast.CodeNode;
import polyglot.ast.ConstructorCall;
import polyglot.ast.ConstructorDecl;
import polyglot.ast.Expr;
import polyglot.ast.New;
import polyglot.ast.Node;
import polyglot.ast.ProcedureDecl;
import polyglot.ast.Special;
import polyglot.main.Report;
import polyglot.types.MethodInstance;
import polyglot.types.ProcedureInstance;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.util.InternalCompilerError;
import accrue.AccrueExtensionInfo;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtConstructorDecl;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisUnit;
import accrue.analysis.interprocanalysis.AnalysisUtil;
import accrue.analysis.interprocanalysis.ExitMap;
import accrue.analysis.interprocanalysis.WorkQueue;
import accrue.analysis.pointer.HContext;

public class CtlDepAnalysisUtil extends AnalysisUtil<CDAbsVal> {

    public CtlDepAnalysisUtil(WorkQueue<CDAbsVal> workQueue,
            AnalysisUnit currentAnalysisUnit, AccrueExtensionInfo extInfo) {
        super(workQueue, currentAnalysisUnit, extInfo);
    }

    @Override
    public ExitMap<CDAbsVal> analyze(CodeNode n, CDAbsVal before)
            throws SemanticException {
        CtlDepDataFlow icfgb =
                new CtlDepDataFlow(this,
                                   extInfo.scheduler().currentJob(),
                                   extInfo.typeSystem(),
                                   extInfo.nodeFactory());
        icfgb.dataflow(n, CDAbsVal.BOTTOM);
        return createExitMap(CDAbsVal.BOTTOM);
    }

    @Override
    public ExitMap<CDAbsVal> analyzeInitializers(CDAbsVal input)
            throws SemanticException {
        super.analyzeInitializers(CDAbsVal.BOTTOM);
        return createExitMap(CDAbsVal.BOTTOM);
    }

    public ExitMap<CDAbsVal> call(Call n) {
        MethodInstance mi = n.methodInstance();
        boolean isStaticCall = mi.flags().isStatic();
        if (isStaticCall) {
            return callNonVirtual(mi,
                                  workQueue.factory()
                                           .createAnalysisContext(this,
                                                                  mi,
                                                                  null,
                                                                  n),
                                  n);
        }
        else {
            // this is a non-static call (and may be
            // a virtual call, or a call to super.m())
            // loop over the possible receivers.
            Expr receiver = (Expr) n.target();
            Set<HContext> pointsTo = pointsTo(receiver);

            if (pointsTo == null) {
                throw new InternalCompilerError("No points to info for the receiver: "
                        + n);
            }
            for (HContext o : pointsTo) {
                // analyze the implementing method
                Type receiverType = o.type();
                MethodInstance omi = null;
                if (receiver instanceof Special
                        && ((Special) receiver).kind() == Special.SUPER) {
                    // it's a call to super.m(). Don't dynamically dispatch.
                    omi = mi;
                }
                else if (receiverType != null) {
                    // it's a virtual call, so find
                    // the appropriate implementing method.
                    omi =
                            extInfo.typeSystem()
                                   .findImplementingMethod(receiverType.toClass(),
                                                           mi);
                }
                else {
                    // System.err.println(" *** no receiver for virtual method invocation to "
                    // + mi + " " + n.position());
                    throw new InternalCompilerError("no receiver for virtual method invocation to "
                                                            + mi,
                                                    n.position());
                }
                if (omi == null) {
                    // no implementing method!
                    // we'll see if we have a signature for mi
                    omi = mi;
                }
                callNonVirtual(omi,
                               workQueue.factory().createAnalysisContext(this,
                                                                         omi,
                                                                         o,
                                                                         n),
                               n);
            }
            if (pointsTo.isEmpty()) {
                // BOTTOM
                // System.err.println(" *** Receiver doesn't point to anything! May be a bug in the program? ");
                // System.err.println("     " + n + " " + mi.container()+"."+
                // mi.name() + "  at " + n.position());
            }
            return createExitMap(CDAbsVal.BOTTOM);
        }

        // Call n = (Call) callSite.getNode();
        // if (mi.flags().isStatic()) {
        // return callNonVirtual(mi, workQueue.factory()
        // .createAnalysisContext(this, mi, null, n), callSite,
        // callSiteDFI);
        // } else {
        // ExitMap<ICDGAbsVal> res = createExitMap(ICDGAbsVal.BOTTOM);
        // Set<EdgeIdentifier> targets = virtualCallTargets(mi,callSite);
        // if (Report.should_report(workQueue().factory().reportTopics(), 5)) {
        // Report.report(5, "Virtual call " + mi + " has "
        // + targets.size() + " targets");
        // }
        // for (EdgeIdentifier edge : targets) {
        // ProcedureInstance pi;
        // if (!edge.getTarget().isNative()) {
        // CodeDecl cd = (CodeDecl) edge.getTarget().getNode();
        // pi = (ProcedureInstance) cd.codeInstance();
        // } else {
        // NativeNodeIdentifier mc = (NativeNodeIdentifier) edge.getTarget();
        // pi = (ProcedureInstance) mc.getCodeInstance();
        // }
        // res = res.upperBound(callNonVirtual(pi,
        // edge.getTarget().getContext(),
        // callSite, callSiteDFI));
        // }
        // return res;
        // }
    }

    public ExitMap<CDAbsVal> call(New n) {
        Set<HContext> pointsTo = pointsTo(n);
        if (pointsTo.size() > 1) {
            throw new InternalCompilerError("Invocation to constructor does not have unique pointsto: "
                                                    + pointsTo,
                                            n.position());
        }
        if (pointsTo.isEmpty()) {
            throw new InternalCompilerError("Constructor invocation doesn't point to anything!",
                                            n.position());

        }
        return callNonVirtual(n.constructorInstance(),
                              workQueue.factory()
                                       .createAnalysisContext(this,
                                                              n.constructorInstance(),
                                                              pointsTo.iterator()
                                                                      .next(),
                                                              n),
                              n);
    }

    public ExitMap<CDAbsVal> call(ConstructorCall n) {
        ConstructorDecl cd =
                (ConstructorDecl) this.currentAnalysisUnit().codeNode();
        ExtConstructorDecl ext = (ExtConstructorDecl) AccrueExt_c.ext(cd);
        Set<HContext> pointsTo =
                pointsTo(ext.getThisNode(), this.currentContext(), this.extInfo);
        if (pointsTo.isEmpty()) {
            throw new InternalCompilerError("Constructor invocation doesn't point to anything!",
                                            n.position());

        }
        else {
            for (HContext o : pointsTo) {
                callNonVirtual(n.constructorInstance(),
                               workQueue.factory()
                                        .createAnalysisContext(this,
                                                               n.constructorInstance(),
                                                               o,
                                                               n),
                               n);
            }
            return createExitMap(CDAbsVal.BOTTOM);
        }
    }

    /**
     * A non-virtual call to a method instance
     * 
     * @return
     */
    protected ExitMap<CDAbsVal> callNonVirtual(ProcedureInstance pi,
            AnalysisContext calleeContext, Node n) {
        // get the implementing method body to add the argument names...
        ProcedureDecl pd = (ProcedureDecl) workQueue.registrar().getCode(pi);
        AnalysisUnit callee = null;
        if (Report.should_report(workQueue().factory().reportTopics(), 5)) {
            Report.report(3, "Processing call to " + pd);
        }

        if (pd == null || pd.body() == null) {
            // nothing to do...
            return createExitMap(CDAbsVal.BOTTOM);
        }
        else {
            callee = new AnalysisUnit(pd, calleeContext);
            try {
                return this.workQueue.getAnalysisResult(callee,
                                                        CDAbsVal.BOTTOM,
                                                        this);
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Unexpected semantic exception",
                                                e);
            }
        }
    }

    public boolean dropBackEdges() {
        return true;
    }
}
