package accrue.analysis.ext;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import polyglot.ast.CodeNode;
import polyglot.ast.ConstructorCall;
import polyglot.ast.Expr;
import polyglot.types.ClassType;
import polyglot.types.ConstructorInstance;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.SerialVersionUID;
import polyglot.visit.DataFlow;
import polyglot.visit.FlowGraph;
import polyglot.visit.FlowGraph.EdgeKey;
import polyglot.visit.FlowGraph.Peer;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisContextMap;
import accrue.analysis.interprocanalysis.DataFlowDispatch;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.ReferenceVariable;
import accrue.analysis.pointer.RegisterPointerStmtsVisitor;
import accrue.analysis.pointer.StmtRegistrar;
import accrue.analysis.pointer.StmtRegistrar.ExceptionHandlerReferenceVariable;
import accrue.analysis.preciseExceptions.ThrowableType;

public class ExtConstructorCall extends ExtTerm implements ExtProcedureCall {
    private static final long serialVersionUID = SerialVersionUID.generate();
    private CallSiteNode callSiteNode;
    private final AnalysisContextMap<Set<ThrowableType>> throwTypes =
            new AnalysisContextMap<Set<ThrowableType>>();
    private final AnalysisContextMap<Boolean> canTerminateNormally =
            new AnalysisContextMap<Boolean>();

    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {

        ConstructorCall c = (ConstructorCall) this.node();

        List<ExceptionHandlerReferenceVariable> exceptionHandlers =
                v.exceptionHandlers();
        Map<Type, ReferenceVariable> methodThrows =
                v.currentCode().exceptionRets;

        ConstructorInstance ci = c.constructorInstance();

        // build up localnodes for the actual args
        List<ReferenceVariable> actuals =
                new ArrayList<ReferenceVariable>(c.arguments().size());
        for (Expr arg : c.arguments()) {
            if (arg.type().isReference() || arg.type().isNull()) {
                actuals.add(v.getReferenceVariable(arg));
            }
        }

        registrar.addConstructorCall(v.currentCode().thisNode,
                                     ci,
                                     this,
                                     actuals,
                                     exceptionHandlers,
                                     methodThrows,
                                     c.position(),
                                     v.currentCode());

        if (c.kind().equals(ConstructorCall.SUPER)) {
            // we had a super call, so immediately after the super,
            // we analyze every instance initializer.
            ClassType container =
                    ((ConstructorInstance) v.currentCode().codeInstance).container()
                                                                        .toClass();
            for (CodeNode init : registrar.getInstanceInitializers(container)) {
                if (!init.codeInstance().flags().isStatic()) {
                    v.visitInstanceIntializer(init);
                }
            }
        }

    }

    @Override
    public void recordPreciseExResults(AnalysisContext context,
            Set<ThrowableType> throwTypes, boolean canTerminateNormally) {
        this.throwTypes.put(context, throwTypes);
        this.canTerminateNormally.put(context, canTerminateNormally);
    }

    @Override
    public Collection<ThrowableType> throwTypes(TypeSystem ts,
            AnalysisContext context) {
        Set<ThrowableType> s = throwTypes.get(context);
        if (s == null) {
            return super.throwTypes(ts, context);
        }
        return s;
    }

    @Override
    public boolean canTerminateNormally(AnalysisContext context) {
        Boolean b = canTerminateNormally.get(context);
        if (b == null) return true;
        return b.booleanValue();
    }

    @Override
    public <DFItem extends DataFlow.Item> Map<EdgeKey, DFItem> flowDispatch(
            DataFlowDispatch<DFItem> dataflow, DFItem dfIn,
            FlowGraph<DFItem> graph, Peer<DFItem> peer) {
        ConstructorCall n = (ConstructorCall) this.node();
        return dataflow.flowConstructorCall(n, dfIn, graph, peer);
    }

    @Override
    public void setCallSiteNode(CallSiteNode callSiteNode) {
        this.callSiteNode = callSiteNode;
    }

    @Override
    public CallSiteNode getCallSiteNode() {
        return this.callSiteNode;
    }

}
