package accrue.jl5.ext;

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

import polyglot.ast.Expr;
import polyglot.ext.jl5.ast.EnumConstantDecl;
import polyglot.types.ConstructorInstance;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
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.ext.ExtAlloc;
import accrue.analysis.ext.ExtProcedureCall;
import accrue.analysis.ext.ExtTerm;
import accrue.analysis.interprocanalysis.AnalysisContext;
import accrue.analysis.interprocanalysis.AnalysisContextMap;
import accrue.analysis.interprocanalysis.DataFlowDispatch;
import accrue.analysis.pointer.AllocSiteNode;
import accrue.analysis.pointer.CallSiteNode;
import accrue.analysis.pointer.ConstructorContext;
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;
import accrue.jl5.JL5DataFlowDispatch;
import accrue.jl5.pointer.JL5StmtRegistrar;

public class ExtEnumConstantDecl extends ExtTerm implements ExtAlloc,
        ExtProcedureCall {
    private static final long serialVersionUID = SerialVersionUID.generate();

    private Map<ConstructorContext, AllocSiteNode> allocNodes;

//    private Map<ConstructorContext, LocalNode> localNodes;
    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) {

        JL5StmtRegistrar jl5registrar = (JL5StmtRegistrar) registrar;
        EnumConstantDecl ecd = (EnumConstantDecl) this.node();

        // treat it like C.foo = new C(...)
        jl5registrar.registerEnumConstantDecl(ecd,
                                              v.currentCode(),
                                              v.constructorContext());

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

        ConstructorInstance ci = ecd.constructorInstance();

        // build up localnodes for the actual args
        List<ReferenceVariable> actuals = new ArrayList<ReferenceVariable>(ecd.args().size());
        for (Expr arg : ecd.args()) {
            if (arg.type().isReference() || arg.type().isNull()) {
                ReferenceVariable ln = v.getReferenceVariable(arg);
                if (ln == null) {
                    throw new InternalCompilerError("Null local node " + arg);
                }
                actuals.add(ln);
            }
        }

        registrar.addConstructorCall(jl5registrar.nodeForEnumConstantDecl(ecd.enumInstance()),
                                     ci,
                                     this,
                                     actuals,
                                     exceptionHandlers,
                                     methodThrows,
                                     ecd.position(),
                                     v.currentCode());

    }

    @Override
    public <DFItem extends DataFlow.Item> Map<EdgeKey, DFItem> flowDispatch(
            DataFlowDispatch<DFItem> d, DFItem dfIn, FlowGraph<DFItem> graph,
            Peer<DFItem> peer) {
        EnumConstantDecl n = (EnumConstantDecl) this.node();
        if (d instanceof JL5DataFlowDispatch) {
            JL5DataFlowDispatch<DFItem> jl5d = (JL5DataFlowDispatch<DFItem>) d;
            return jl5d.flowEnumConstantDecl(n, dfIn, graph, peer);
        }
        throw new InternalCompilerError("DataFlowDispatch "
                + d.getClass()
                + " does not support JL5 AST nodes. It should implemetn JL5DataFlowDispatch.");
    }

    @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 void setAllocNode(AllocSiteNode allocNode,
            ConstructorContext constructorContext) {
        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);
    }

//    public void setLocalNode(LocalNode ln, ConstructorContext constructorContext) {
//        if (this.localNodes == null) {
//            this.localNodes =
//                    new LinkedHashMap<ConstructorContext, LocalNode>();
//        }
//        if (this.localNodes.get(constructorContext) != null) {
//            throw new InternalCompilerError("Local nodes should only be set once!");
//        }
//        this.localNodes.put(constructorContext, ln);
//    }

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

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