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

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

import polyglot.main.Report;
import polyglot.types.CodeInstance;
import polyglot.types.MethodInstance;
import polyglot.types.Type;
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 StmtSpecialMethodCall extends StmtProcedureCall {

    final ReferenceVariable result;
    final ReferenceVariable receiver;
    final MethodInstance mi;
    final List<ReferenceVariable> actuals;
    final List<ExceptionHandlerReferenceVariable> exceptionHandlers;
    final Map<Type, ReferenceVariable> procedureThrows;

    public StmtSpecialMethodCall(ReferenceVariable result, ReferenceVariable receiver,
            CallSiteNode callSiteNode, MethodInstance mi,
            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.mi = mi;
        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(mi.signature());
        sb.append(actuals);
        return sb.toString();
    }

    @Override
    public boolean process(CContext callerContext, PointsToGraph g,
            StmtRegistrar registrar, HeapAbstractionFactory af,
            StmtAndContext sac) {

        if (Report.should_report(AnalysisTopics.POINTSTO, 3))
            Report.report(3, "Processing special method call " + this
                    + " in context " + callerContext);

        ReferenceVariableReplica l = null;
        if (result != null) {
            l = ReferenceVariableReplica.create(callerContext, result, af);
        }

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

        boolean changed = false;
        ReferenceVariableReplica r0 =
                ReferenceVariableReplica.create(callerContext, receiver, af);

        for (HContext o : g.pointsTo(sac, r0)) {
            if (Report.should_report(AnalysisTopics.POINTSTO, 4))
                Report.report(3, "   Trying receiver " + o);
            try {
                changed =
                        resolve(callerContext,
                                registrar,
                                g,
                                o,
                                args,
                                l,
                                handlers,
                                procThrows,
                                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 boolean resolve(CContext callerContext, StmtRegistrar registrar,
            PointsToGraph g, HContext o, List<ReferenceVariableReplica> args,
            ReferenceVariableReplica l,
            List<ExceptionHandlerPointsToGraphNode> handlers,
            Map<Type, ReferenceVariableReplica> procThrows,
            HeapAbstractionFactory af, StmtAndContext sac) {

        CodeInfo calleeProcInfo = registrar.getCodeInfo(mi);
        if (calleeProcInfo == null || mi.flags().isNative()) {
            // we don't have code for this constructor
            return registrar.processMissingCode(this.codeInstance,
                                                callerContext,
                                                callSiteNode,
                                                mi,
                                                args,
                                                o,
                                                l,
                                                handlers,
                                                procThrows,
                                                g,
                                                origin,
                                                af,
                                                sac);
        }

        if (calleeProcInfo.formals.size() != args.size()) {
            throw new InternalCompilerError("I don't understand the alg");
        }

        return addEdgesForCall(this.codeInstance,
                               callerContext,
                               callSiteNode,
                               registrar,
                               g,
                               calleeProcInfo,
                               o,
                               args,
                               l,
                               handlers,
                               procThrows,
                               origin,
                               af,
                               sac);

    }

}
