package accrue.analysis.ext;

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

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

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

    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {
        Expr e = (Expr) this.node();
        if (!e.type().isReference()) return;

        ArrayAccessAssign assign = (ArrayAccessAssign) e;

        if (assign.right().type().isNull()) {
            // right only points to null
            return;
        }

        if (!assign.operator().equals(Assign.ASSIGN)) {
            throw new InternalCompilerError("Unsupported assignment operator: "
                    + assign);
        }

        ArrayAccess aa = assign.left();

        registrar.addAssignmentToField(v.getReferenceVariable(aa.array()),
                                       StmtRegistrar.ARRAY_CONTENTS,
                                       aa.array().type().toArray().base(),
                                       v.getReferenceVariable(assign.right()),
                                       assign.position(),
                                       v.currentCode());

        if (v.recordExprResults()) {
            registrar.createFreshReferenceVariable(assign,
                                                   v.constructorContext());
            registrar.addAssignment(v.getReferenceVariable(assign),
                                    v.getReferenceVariable(aa.array()),
                                    StmtRegistrar.ARRAY_CONTENTS,
                                    aa.array().type().toArray().base(),
                                    assign.position(),
                                    v.currentCode());
        }

        // may generate exceptions
        TypeSystem ts = v.typeSystem();

        for (Type tt : ((JLang) v.lang()).throwTypes(assign, ts)) {
            registrar.addGeneratedExceptionAssignments(Collections.singleton(tt.toClass()),
                                                       assign,
                                                       v);
        }
    }

    @Override
    public int numArgumentsOnResultStack() {
        Assign n = (Assign) this.node();
        if (n.operator() == Assign.ASSIGN) return 3;
        return 2;
    }

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

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

        for (Type tt : context.lang().throwTypes(aaa, ts)) {
            if (ts.NullPointerException().equals(tt)) {
                ExtExpr et = (ExtExpr) AccrueExt_c.ext(array);
                if (et.mayBeNull(context)) {
                    ThrowableType npe =
                            new ThrowableType(ts.NullPointerException(), true);
                    throwTypes.add(npe);
                }
                continue;
            }
            throwTypes.add(new ThrowableType(tt, true));
        }

        return throwTypes;
    }

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

}
