package accrue.analysis.readwritesets;

import java.util.HashSet;
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.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.ext.AccrueExt_c;
import accrue.analysis.ext.ExtConstructorDecl;
import accrue.analysis.interprocanalysis.AbstractLocation;
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;

/**
 * {@link AnalysisUtil} for an analysis that gathers the set of locations read 
 * and written by an analysis unit.
 */
public class ReadWriteSetsAnalysisUtil extends AnalysisUtil<ReadWriteSet> {

    /**
     * Make a new analysis helper class
     * 
     * @param workQueue 
     *          Work queue for the analysis
     * @param currentAnalysisUnit
     *          Method and context currently being analyzed
     * @param extInfo
     *          Accrue extension information
     */
    public ReadWriteSetsAnalysisUtil(WorkQueue<ReadWriteSet> workQueue,
            AnalysisUnit currentAnalysisUnit, AccrueExtensionInfo extInfo) {
        super(workQueue, currentAnalysisUnit, extInfo);
    }

    @Override
    public ExitMap<ReadWriteSet> analyze(CodeNode n, ReadWriteSet before)
            throws SemanticException {
        ReadWriteSetVisitor v = new ReadWriteSetVisitor(this);
        return createExitMap(v.performVisit(n));
    }

    /**
     * Analyze a method call
     * 
     * @param mi 
     *          method being analyzed
     * @param n 
     *          AST node for the method call
     *          
     * @return results of the analysis
     */
    public ReadWriteSet call(MethodInstance mi, Call n) {

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

            // analyze all method implementations that may potentially be called, and
            // take an upper bound of the result.
            Set<AbstractLocation> readSet = new HashSet<AbstractLocation>();
            Set<AbstractLocation> writeSet = new HashSet<AbstractLocation>();
            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;
                }
                ReadWriteSet dfi =
                        callNonVirtual(omi,
                                       workQueue.factory()
                                                .createAnalysisContext(this,
                                                                       omi,
                                                                       o,
                                                                       n));
                readSet.addAll(dfi.readSet());
                writeSet.addAll(dfi.writeSet());
            }

            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());
                // let's just do the best we can...
                ReadWriteSet dfi = this.guessAnalysisForMissingCode(mi, null);
                readSet.addAll(dfi.readSet());
                writeSet.addAll(dfi.writeSet());
            }

            return new ReadWriteSet(readSet, writeSet);
        }
    }

    /**
     * Process a constructor call from a new expression
     * 
     * @param ci 
     *          constructor being called
     * @param n 
     *          AST node for the constructor call
     *          
     * @return analysis results 
     */
    public ReadWriteSet 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());

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

    /**
     * Process a constructor call to this() or super(), i.e., from within a constructor.
     * 
     * @param ci 
     *          constructor being called
     * @param n
     *          AST node for the constructor call
     * @return analysis results
     */
    public ReadWriteSet 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 {
            Set<AbstractLocation> readSet = new HashSet<AbstractLocation>();
            Set<AbstractLocation> writeSet = new HashSet<AbstractLocation>();
            for (HContext o : pointsTo) {
                ReadWriteSet dfi =
                        callNonVirtual(ci,
                                       workQueue.factory()
                                                .createAnalysisContext(this,
                                                                       ci,
                                                                       o,
                                                                       n));
                readSet.addAll(dfi.readSet());
                writeSet.addAll(dfi.writeSet());
            }
            return new ReadWriteSet(readSet, writeSet);
        }
    }

    /**
     * A non-virtual call to a method instance
     * 
     * @param pi
     *          Procedure being called
     * @param calleeContext
     *          Analysis context for the callee
     * @return analysis results
     */
    private ReadWriteSet 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) {
            recordMissingCode(pi);
            return guessAnalysisForMissingCode(pi, calleeContext);
        }
        else {

            callee = new AnalysisUnit(pd, calleeContext);

            try {
                ExitMap<ReadWriteSet> r =
                        this.workQueue.getAnalysisResult(callee,
                                                         ReadWriteSet.EMPTY,
                                                         this);
                return r.normalTermination();
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Unexpected semantic exception",
                                                e);
            }

        }
    }

    /**
     * Get the analysis results for code with no source or signature
     * 
     * @param pi 
     *          procedure we are missing code for
     * @param calleeContext 
     *          context for the callee of the method
     *          
     * @return analysis result
     */
    private ReadWriteSet guessAnalysisForMissingCode(ProcedureInstance pi,
            AnalysisContext calleeContext) {
        // We'll assume the method does not change any heap locations
        // This could be a source of imprecision
        return ReadWriteSet.EMPTY;
    }
}
