/**
 * 
 */
package accrue.analysis.pointer;

import java.util.List;
import java.util.Map;

import polyglot.main.Report;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.MethodInstance;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import accrue.analysis.AnalysisTopics;
import accrue.analysis.pointer.PointsToEngine.StmtAndContext;
import accrue.analysis.pointer.StmtRegistrar.CodeInfo;
import accrue.analysis.pointer.StmtRegistrar.ExceptionHandlerReferenceVariable;
import accrue.analysis.pointer.analyses.HeapAbstractionFactory;

public class StmtVirtualMethodCall extends StmtProcedureCall {
    final protected ReferenceVariable result;
    final protected ReferenceVariable receiver;
    final protected MethodInstance pi;
    final protected List<ReferenceVariable> actuals;
    final protected List<ExceptionHandlerReferenceVariable> exceptionHandlers;
    final protected Map<Type, ReferenceVariable> procedureThrows;

    public StmtVirtualMethodCall(ReferenceVariable result, ReferenceVariable receiver,
            CallSiteNode callSiteNode, MethodInstance pi,
            List<ReferenceVariable> actuals,
            List<ExceptionHandlerReferenceVariable> exceptionHandlers,
            Map<Type, ReferenceVariable> procedureThrows, Position origin,
            CodeInstance code) {
        super(origin, code, callSiteNode);
        this.result = result;
        this.receiver = receiver;
        this.pi = pi;
        this.actuals = actuals;
        this.exceptionHandlers = exceptionHandlers;
        this.procedureThrows = procedureThrows;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        if (result != null) {
            sb.append(result + "=");
        }
        sb.append(receiver);
        sb.append(".");
        sb.append(pi.signature());
        sb.append(actuals);
        return sb.toString();
    }

    @Override
    public boolean process(CContext context, PointsToGraph g,
            StmtRegistrar registrar, HeapAbstractionFactory af,
            StmtAndContext sac) {
        ReferenceVariableReplica l = null;
        if (result != null) {
            l = ReferenceVariableReplica.create(context, result, af);
        }
        ReferenceVariableReplica r0 =
                ReferenceVariableReplica.create(context, receiver, af);
        List<ReferenceVariableReplica> args = convert(context, actuals, af);

        List<ExceptionHandlerPointsToGraphNode> handlers =
                convert2(context, exceptionHandlers, af);
        Map<Type, ReferenceVariableReplica> procThrows =
                convert(context, procedureThrows, af);

        return process(l,
                       r0,
                       callSiteNode,
                       pi,
                       args,
                       handlers,
                       procThrows,
                       this.codeInstance,
                       context,
                       g,
                       registrar,
                       origin,
                       af,
                       sac);
    }

    public static boolean process(final ReferenceVariableReplica result,
            final ReferenceVariableReplica receiver,
            final CallSiteNode callSiteNode, final MethodInstance pi,
            final List<ReferenceVariableReplica> args,
            final List<ExceptionHandlerPointsToGraphNode> exHandlers,
            final Map<Type, ReferenceVariableReplica> procThrows,
            CodeInstance containingCode, CContext context, PointsToGraph g,
            StmtRegistrar registrar, Position origin,
            HeapAbstractionFactory af, StmtAndContext sac) {

        if (Report.should_report(AnalysisTopics.POINTSTO, 3))
            Report.report(3, "Processing virtual method call to " + pi);

        boolean changed = false;
        for (HContext o : g.pointsTo(sac, receiver)) {
            if (Report.should_report(AnalysisTopics.POINTSTO, 4))
                Report.report(3, "   Trying receiver " + o);
            try {
                changed =
                        resolve(containingCode,
                                context,
                                callSiteNode,
                                registrar,
                                g,
                                pi,
                                o,
                                args,
                                result,
                                exHandlers,
                                procThrows,
                                origin,
                                af,
                                sac) || changed;
            }
            catch (RuntimeException e) {
                System.err.println("Caught runtime exception for receiver "
                        + o.type() + " at " + origin);
                e.printStackTrace();
                throw e;
            }
        }
        return changed;
    }

    private static boolean resolve(CodeInstance containingCode,
            CContext callerContext, CallSiteNode callSiteNode,
            StmtRegistrar registrar, PointsToGraph g, MethodInstance pi,
            HContext receiver, List<ReferenceVariableReplica> args,
            ReferenceVariableReplica l,
            List<ExceptionHandlerPointsToGraphNode> handlers,
            Map<Type, ReferenceVariableReplica> procThrows, Position origin,
            HeapAbstractionFactory af, StmtAndContext sac) {
        TypeSystem ts = pi.typeSystem();

        Type calleeType = receiver.type();
        ClassType co = calleeType.toClass();
        // the class of o is co. 
        // class Co should implement pi

        if (calleeType.isArray()) {
            // *sigh* it's a method call on an array.
            // let's just lie and say the implementing method is to be found in Object...
            co = ts.Object();
        }

        if (co == null) {
            throw new InternalCompilerError("We have an object of non-class, non-array type: "
                                                    + receiver,
                                            origin);
        }

        MethodInstance imi = ts.findImplementingMethod(co, pi);
        if (imi == null) {
            // we don't have an implementing method.
            // let's see if we have a signature for pi.
            imi = pi;
            //throw new InternalCompilerError("No implementing method for " + pi + " in class " + co, origin);            
        }
        CodeInfo calleeProcInfo = registrar.getCodeInfo(imi);
        if (calleeProcInfo == null || imi.flags().isNative()) {
            // we don't have code for this method
            return registrar.processMissingCode(containingCode,
                                                callerContext,
                                                callSiteNode,
                                                imi,
                                                args,
                                                receiver,
                                                l,
                                                handlers,
                                                procThrows,
                                                g,
                                                origin,
                                                af,
                                                sac);
        }
//        if (calleeProcInfo.formals.size() != args.size()) {
//            throw new InternalCompilerError("I don't understand the alg");            
//        }
        return addEdgesForCall(containingCode,
                               callerContext,
                               callSiteNode,
                               registrar,
                               g,
                               calleeProcInfo,
                               receiver,
                               args,
                               l,
                               handlers,
                               procThrows,
                               origin,
                               af,
                               sac);
    }

}
