package accrue.analysis.domination;

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.ProcedureDecl;
import polyglot.ast.Special;
import polyglot.main.Report;
import polyglot.types.ConstructorInstance;
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.domination.DominationPass.Kind;
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.Unit;
import accrue.analysis.interprocanalysis.WorkQueue;
import accrue.analysis.pointer.HContext;

public class DominationAnalysisUtil extends AnalysisUtil<Unit> {
    private final Kind kind;

    public DominationAnalysisUtil(WorkQueue<Unit> workQueue,
            AnalysisUnit currentAnalysisUnit, AccrueExtensionInfo extInfo,
            Kind kind) {
        super(workQueue, currentAnalysisUnit, extInfo);
        this.kind = kind;
    }

    @Override
    public ExitMap<Unit> analyze(CodeNode n, Unit before)
            throws SemanticException {

        if (Report.should_report(this.workQueue.factory().reportTopics(), 2))
            Report.report(1,
                          "DominationAnalysisUtil.analyzeImpl: about to perform dataflow for "
                                  + n.codeInstance());
        if (kind == Kind.DOMINATION) {
            DominatorDataFlow d =
                    new DominatorDataFlow(this.extInfo.scheduler().currentJob(),
                                          this.extInfo.typeSystem(),
                                          this.extInfo.nodeFactory(),
                                          this);
            d.performDataflow(n);
        }
        else if (kind == Kind.POSTDOMINATION) {
            PostDominatorDataFlow d =
                    new PostDominatorDataFlow(this.extInfo.scheduler()
                                                          .currentJob(),
                                              this.extInfo.typeSystem(),
                                              this.extInfo.nodeFactory(),
                                              this);
            d.performDataflow(n);
        }
        else if (kind == Kind.NONFATALPOSTDOMINATION) {
            PostDominatorDataFlow d =
                    new NonFatalPostDominatorDataFlow(this.extInfo.scheduler()
                                                                  .currentJob(),
                                                      this.extInfo.typeSystem(),
                                                      this.extInfo.nodeFactory(),
                                                      this);
            d.performDataflow(n);
        }

        if (Report.should_report(this.workQueue.factory().reportTopics(), 2))
            Report.report(1,
                          "DominationAnalysisUtil.analyzeImpl: finished dataflow for "
                                  + n.codeInstance());

        return this.createExitMap(Unit.UNIT);
    }

    public void call(MethodInstance mi, Call n) {
        boolean isStaticCall = mi.flags().isStatic();
        if (isStaticCall) {
            callNonVirtual(mi,
                           workQueue.factory().createAnalysisContext(this,
                                                                     mi,
                                                                     null,
                                                                     n));
            return;
        }
        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);
            }

            // analyze all method implementations that may potentially be called
            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! Just use mi
                    omi = mi;
                }
                callNonVirtual(omi,
                               workQueue.factory().createAnalysisContext(this,
                                                                         omi,
                                                                         o,
                                                                         n));
            }
            if (pointsTo.isEmpty()) {
                // XXX UNCOMMENT THESE
                //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());
            }
        }
    }

    public void call(ConstructorInstance ci, 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());

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

    public void constructorCall(ConstructorInstance ci, 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(ci,
                               workQueue.factory().createAnalysisContext(this,
                                                                         ci,
                                                                         o,
                                                                         n));
            }
        }
    }

    /**
     * A non-virtual call to a method instance
     * @return
     */
    private void callNonVirtual(ProcedureInstance pi,
            AnalysisContext calleeContext) {
        // 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, Unit.UNIT, this);
                // we don't actually need to do anything with the result
                // the point was to get the work queue to process the
                // callee if it hasn't already.
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Unexpected semantic exception",
                                                e);
            }

        }
    }
}
