package accrue.analysis.ext;

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

import polyglot.ast.Assign;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.FieldAssign;
import polyglot.ast.JLang;
import polyglot.ast.Receiver;
import polyglot.types.FieldInstance;
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 ExtFieldAssign 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;

        FieldAssign fa = (FieldAssign) e;

        Field f = fa.left();

        FieldInstance fi = f.fieldInstance();

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

        if (fi.flags().isStatic()) {
            registrar.addAssignment(registrar.nodeForStaticField(fi),
                                    v.getReferenceVariable(fa.right()),
                                    fa.position(),
                                    v.currentCode());
        }
        else {
            registrar.addAssignmentToField(v.getReferenceVariable((Expr) f.target()),
                                           fi,
                                           v.getReferenceVariable(fa.right()),
                                           fa.position(),
                                           v.currentCode());
        }

        if (v.recordExprResults()) {
            registrar.createFreshReferenceVariable(fa, v.constructorContext());
            if (fi.flags().isStatic()) {
                registrar.addAssignment(v.getReferenceVariable(fa),
                                        registrar.nodeForStaticField(fi),
                                        fa.position(),
                                        v.currentCode());
            }
            else {
                registrar.addAssignment(v.getReferenceVariable(fa),
                                        v.getReferenceVariable((Expr) f.target()),
                                        fi,
                                        fa.position(),
                                        v.currentCode());
            }
        }

    }

    @Override
    public int numArgumentsOnResultStack() {
        Assign n = (Assign) this.node();
        if (n.operator() == Assign.ASSIGN
                && !(((FieldAssign) n).left().target() instanceof Expr)) {
            return 1;
        }
        return 2;
    }

    @Override
    public Collection<ThrowableType> throwTypes(TypeSystem ts,
            AnalysisContext context) {
        FieldAssign fa = (FieldAssign) this.node();
        Field f = fa.left();
        Receiver target = f.target();
        if (target instanceof Expr) {
            ExtExpr et = (ExtExpr) AccrueExt_c.ext(target);
            if (et.mayBeNull(context)) {
                ThrowableType npe =
                        new ThrowableType(ts.NullPointerException(), true);
                return Collections.singletonList(npe);
            }
        }
        return Collections.emptyList();
    }

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

}
