package accrue.analysis.ext;

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

import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.JLang;
import polyglot.ast.Receiver;
import polyglot.types.FieldInstance;
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 ExtField extends ExtExpr {
    private static final long serialVersionUID = SerialVersionUID.generate();

    @Override
    public void registerPointerStmts(StmtRegistrar registrar,
            RegisterPointerStmtsVisitor v) {
        Expr e = (Expr) this.node();
        Field f = (Field) e;
        FieldInstance fi = f.fieldInstance();

        if (v.recordExprResults() && e.type().isReference()) {
            registrar.createFreshReferenceVariable(f, v.constructorContext());

            if (fi.flags().isStatic()) {
                registrar.addAssignment(v.getReferenceVariable(e),
                                        registrar.nodeForStaticField(fi),
                                        f.position(),
                                        v.currentCode());
            }
            else {
                registrar.addAssignment(v.getReferenceVariable(e),
                                        v.getReferenceVariable((Expr) f.target()),
                                        fi,
                                        f.position(),
                                        v.currentCode());
            }
        }
        if (!fi.flags().isStatic()) {
            // may generate a null pointer exception if the target is null.
            ExtExpr targ = (ExtExpr) AccrueExt_c.ext(f.target());
            // we have no context here...
            if (targ.mayBeNull(null, null)) {
                TypeSystem ts = v.typeSystem();
                registrar.addGeneratedExceptionAssignments(Collections.singleton(ts.NullPointerException()),
                                                           e,
                                                           v);
            }
        }
    }

    @Override
    public Collection<ThrowableType> throwTypes(TypeSystem ts,
            AnalysisContext context) {
        Receiver target = ((Field) this.node()).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> d, DFItem dfIn, FlowGraph<DFItem> graph,
            Peer<DFItem> peer) {
        Field n = (Field) this.node();
        return d.flowField(n, dfIn, graph, peer);
    }

}
