package accrue.analysis.ext;

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

import polyglot.ast.Binary;
import polyglot.ast.BooleanLit;
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 ExtBinary extends ExtExpr {
    private static final long serialVersionUID = SerialVersionUID.generate();

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

        if (mayThrowArithmeticException(null)) {
            // May throw an Arithmetic Exception
            TypeSystem ts = v.typeSystem();
            registrar.addGeneratedExceptionAssignments(Collections.singleton(ts.ArithmeticException()),
                                                       b,
                                                       v);
        }

        if (!b.type().isReference()) return;

        TypeSystem ts = v.typeSystem();

        if (!ts.String().equals(b.type())) {
            throw new InternalCompilerError("Unknown binary " + b);
        }

        if (v.recordExprResults()) {
            // Treat it as a special kind of alloc site for now.
            registrar.createFreshReferenceVariable(b, v.constructorContext());
            registrar.registerStringLiteral(b,
                                            v.currentCode(),
                                            v.constructorContext());
        }
    }

    @Override
    public int numArgumentsOnResultStack() {
        Binary b = (Binary) this.node;
        if (b.operator() == Binary.COND_AND || b.operator() == Binary.COND_OR) {
            // short circuit evaluation.
            if (b.left() instanceof BooleanLit) {
                BooleanLit bl = (BooleanLit) b.left();
                if ((bl.value() && b.operator() == Binary.COND_OR)
                        || (!bl.value() && b.operator() == Binary.COND_AND)) {
                    return 1;
                }
            }
        }
        return 2;
    }

    @Override
    public boolean mayBeNull(AnalysisContext context) {
        // can never be null.
        return false;
    }

    @Override
    public Collection<ThrowableType> throwTypes(TypeSystem ts,
            AnalysisContext context) {
        if (mayThrowArithmeticException(context)) {
            return Collections.singleton(new ThrowableType(ts.ArithmeticException(),
                                                           true));
        }
        return Collections.emptySet();
    }

    protected boolean mayThrowArithmeticException(AnalysisContext context) {
        Binary b = (Binary) this.node();
        if (b.operator() == Binary.DIV || b.operator() == Binary.MOD) {
            Type t = b.type();
            if (t.isNumeric() && !(t.isDouble() || t.isFloat())) {
                // may throw an ArithmeticException, unless we know that the right operand is non-zero
                if (b.right().isConstant()
                        && b.right().constantValueSet()
                        && ((Number) b.right().constantValue()).longValue() != 0) {
                    // rhs is a non-zero constant!
                    return false;
                }
                return true;
            }
        }
        return false;
    }

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

}
