package accrue.analysis.ext;

import java.util.LinkedHashMap;
import java.util.Map;

import polyglot.ast.ArrayInit;
import polyglot.ast.Expr;
import polyglot.util.InternalCompilerError;
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.DataFlowDispatch;
import accrue.analysis.pointer.AllocSiteNode;
import accrue.analysis.pointer.ConstructorContext;
import accrue.analysis.pointer.RegisterPointerStmtsVisitor;
import accrue.analysis.pointer.StmtRegistrar;

public class ExtArrayInit extends ExtExpr implements ExtAlloc {
    private static final long serialVersionUID = SerialVersionUID.generate();

    private Map<ConstructorContext, AllocSiteNode> allocNodes;

    @Override
    public void setAllocNode(AllocSiteNode allocNode,
            ConstructorContext constructorContext) {
        Expr e = (Expr) this.node();
        if (!e.type().isReference()) {
            throw new InternalCompilerError("Local nodes only for reference types");
        }
        if (this.allocNodes == null) {
            this.allocNodes =
                    new LinkedHashMap<ConstructorContext, AllocSiteNode>();
        }
        if (this.allocNodes.get(constructorContext) != null) {
            throw new InternalCompilerError("Alloc nodes should only be set once!");
        }
        this.allocNodes.put(constructorContext, allocNode);
    }

    @Override
    public AllocSiteNode getAllocNode(ConstructorContext constructorContext) {
        if (this.allocNodes == null) return null;
        return this.allocNodes.get(constructorContext);
    }

    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {
        ArrayInit n = (ArrayInit) this.node();
        registrar.createFreshReferenceVariable(n, "array-init", v.constructorContext());
        registrar.registerAlloc(this, n.type().toArray(), "array-init-alloc-"
                + n.toString(), v.currentCode(), v.constructorContext());

        // if the initialization expressions are non-empty and are reference
        // types, then add assignments...
        if (n.type().isNull() || !n.type().toArray().base().isReference()) {
            return;
        }

        for (Expr init : n.elements()) {
            registrar.addAssignmentToField(v.getReferenceVariable(n),
                                           StmtRegistrar.ARRAY_CONTENTS,
                                           n.type().toArray().base(),
                                           v.getReferenceVariable(init),
                                           init.position(),
                                           v.currentCode());
        }
    }

    @Override
    public boolean mayBeNull(AnalysisContext context) {
        return false;
    }

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

    @Override
    public int numArgumentsOnResultStack() {
        return ((ArrayInit) this.node()).elements().size();
    }
}
