package accrue.analysis.ext;

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

import polyglot.ast.ArrayAccess;
import polyglot.ast.Expr;
import polyglot.ast.JLang;
import polyglot.types.ClassType;
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.DataFlowDispatch;
import accrue.analysis.pointer.RegisterPointerStmtsVisitor;
import accrue.analysis.pointer.StmtRegistrar;
import accrue.analysis.preciseExceptions.ThrowableType;

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

    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {
        ArrayAccess aa = (ArrayAccess) this.node();

        if (!aa.type().isReference()) {
            // nothing to do
            return;
        }
        if (v.recordExprResults()) {
            registrar.createFreshReferenceVariable(aa, v.constructorContext());

            registrar.addAssignment(v.getReferenceVariable(aa),
                                    v.getReferenceVariable(aa.array()),
                                    StmtRegistrar.ARRAY_CONTENTS,
                                    aa.array().type().toArray().base(),
                                    aa.position(),
                                    v.currentCode());
        }

        // may generate a null pointer exception if the array is null,
        // or an out of bounds exception
        TypeSystem ts = v.typeSystem();
        Set<ClassType> exs =
                new LinkedHashSet(((JLang) v.lang()).throwTypes(aa, ts));
        registrar.addGeneratedExceptionAssignments(exs, aa, v);

    }

    @Override
    public Collection<ThrowableType> throwTypes(TypeSystem ts,
            AnalysisContext context) {
        ArrayAccess aa = (ArrayAccess) this.node();
        Expr array = aa.array();

        List<ThrowableType> throwTypes = new ArrayList<ThrowableType>(3);

        ExtExpr et = (ExtExpr) AccrueExt_c.ext(array);
        if (et.mayBeNull(context)) {
            ThrowableType npe =
                    new ThrowableType(ts.NullPointerException(), true);
            throwTypes.add(npe);
        }
        throwTypes.add(new ThrowableType(ts.OutOfBoundsException(), true));

        return throwTypes;
    }

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

}
