package accrue.analysis.ast;

import java.util.ArrayList;
import java.util.List;

import polyglot.ast.Expr;
import polyglot.ast.Expr_c;
import polyglot.ast.Node;
import polyglot.ast.Term;
import polyglot.ast.TypeNode;
import polyglot.types.ClassType;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.util.Position;
import polyglot.util.SerialVersionUID;
import polyglot.visit.CFGBuilder;
import polyglot.visit.NodeVisitor;
import polyglot.visit.TypeChecker;
import accrue.analysis.interprocanalysis.PreciseDataFlow.PreciseCFGBuilder;

public class Suppress_c extends Expr_c implements Suppress {
    private static final long serialVersionUID = SerialVersionUID.generate();

    private List<TypeNode> suppressExs;
    private List<ClassType> suppressExTypes;
    private Expr expr;

    public Suppress_c(Position pos, List<TypeNode> suppressExs, Expr expr) {
        super(pos);
        this.suppressExs = suppressExs;
        this.expr = expr;
    }

    @Override
    public boolean isDisambiguated() {
        if (!expr.isDisambiguated() || !super.isDisambiguated()) {
            return false;
        }
        for (TypeNode tn : suppressExs) {
            if (!tn.isDisambiguated()) return false;
        }
        return true;
    }

    @Override
    public Node typeCheck(TypeChecker tc) throws SemanticException {
        suppressExTypes = new ArrayList<ClassType>(this.suppressExs.size());
        for (TypeNode tn : this.suppressExs) {
            suppressExTypes.add(tn.type().toClass());
        }
        return type(expr.type());
    }

    @Override
    public Term firstChild() {
        return this.expr;
    }

    @Override
    public <T> List<T> acceptCFG(CFGBuilder<?> v, List<T> succs) {
        // Pretend that we are like a catch block for all of the exceptions
        // in the suppressExTypes list. This will make sure that any of these
        // exceptions thrown by expr will be caught by this node, and then
        // discarded in the dataflow analysis.
        CFGBuilder<?> v2 = v;
        if (v instanceof PreciseCFGBuilder) {
            PreciseCFGBuilder<?> pcb = (PreciseCFGBuilder<?>) v;
            v2 = pcb.push(this.suppressExTypes, this);
        }
        v2.visitCFG(expr, this, EXIT);
        return succs;
    }

    @Override
    public boolean suppress(Type ct) {
        for (ClassType c : this.suppressExTypes) {
            if (ct.isSubtype(c)) return true;
        }
        return false;
    }

    @Override
    public Node visitChildren(NodeVisitor v) {
        List<TypeNode> l = visitList(this.suppressExs, v);
        Expr expr = (Expr) visitChild(this.expr, v);
        return reconstruct(l, expr);
    }

    private Suppress reconstruct(List<TypeNode> l, Expr expr) {
        if (this.suppressExs == l && this.expr == expr) {
            return this;
        }
        Suppress_c n = (Suppress_c) copy();
        n.suppressExs = l;
        n.expr = expr;
        return n;
    }

    @Override
    public Expr expr() {
        return this.expr;
    }

    @Override
    public List<ClassType> suppressExceptionTypes() {
        return this.suppressExTypes;
    }

    @Override
    public String toString() {
        return "@suppress " + this.suppressExs + " " + this.expr;
    }

}
