package accrue.analysis.domination;

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

import polyglot.ast.Call;
import polyglot.ast.ConstructorCall;
import polyglot.ast.Loop;
import polyglot.ast.New;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.frontend.Job;
import polyglot.main.Report;
import polyglot.types.SemanticException;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.util.SubtypeSet;
import polyglot.visit.FlowGraph;
import polyglot.visit.FlowGraph.Edge;
import polyglot.visit.FlowGraph.EdgeKey;
import polyglot.visit.FlowGraph.ExceptionEdgeKey;
import polyglot.visit.FlowGraph.Peer;
import accrue.analysis.callgraph.PreciseCallGraphFactory;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtTerm;
import accrue.analysis.interprocanalysis.EdgeIdentifier;
import accrue.analysis.interprocanalysis.EdgeIdentifier.EdgeType;
import accrue.analysis.interprocanalysis.EdgeIdentifier.ExceptionEdge;
import accrue.analysis.interprocanalysis.Unit;

/**
 * This class performs a postdominator analysis identical to its parent except
 * that it ignores exceptional control flow edges that are fatal.
 * 
 * Analyses based on these results will be correct for normal program
 * termination.
 */
public class NonFatalPostDominatorDataFlow extends PostDominatorDataFlow {

    public NonFatalPostDominatorDataFlow(Job job, TypeSystem ts,
            NodeFactory nf, DominationAnalysisUtil autil) {
        super(job, ts, nf, autil);
    }

    @Override
    protected Map<EdgeKey, ImmediatePostDominator> flow(
            List<ImmediatePostDominator> inItems, List<EdgeKey> inItemKeys,
            FlowGraph<ImmediatePostDominator> graph,
            Peer<ImmediatePostDominator> peer) {
        Node n = peer.node();

        List<Edge<ImmediatePostDominator>> nonFatalPeers = nonFatalPeers(peer);
        if (graph.startPeers().contains(peer)
                || nonFatalPeers.size() == peer.preds().size()) {
            return super.flow(inItems, inItemKeys, graph, peer);
        }

        // need to find the intersection of the post dominators of all of the
        // preds but ignoring fatal exception edges
        ImmediatePostDominator ipd = intersect(nonFatalPeers, graph);
        if (!peer.isEntry()) {
            if (n instanceof Call) {
                Call c = (Call) n;
                autil.call(c.methodInstance(), c);
            }
            else if (n instanceof ConstructorCall) {
                ConstructorCall cc = (ConstructorCall) n;
                autil.constructorCall(cc.constructorInstance(), cc);
                if (cc.kind().equals(ConstructorCall.SUPER)) {
                    try {
                        autil.analyzeInitializers(Unit.UNIT);
                    }
                    catch (SemanticException e) {
                        throw new InternalCompilerError(e);
                    }
                }
            }
            else if (n instanceof New) {
                New nn = (New) n;
                autil.call(nn.constructorInstance(), nn);
            }
        }

        postdoms.put(peer, ipd);
        return itemToMap(ipd, peer.succEdgeKeys());
    }

    @Override
    protected void check(FlowGraph<ImmediatePostDominator> graph,
            Peer<ImmediatePostDominator> p) throws SemanticException {

        List<Edge<ImmediatePostDominator>> nonFatalPeers = nonFatalPeers(p);
        //record the result if p nonfatally branches
        if (nonFatalPeers.size() > 1
                || (p.isEntry() && p.node() instanceof Loop)
                || (!p.isEntry() && ((p.node() instanceof Call
                        || p.node() instanceof ConstructorCall || p.node() instanceof New)))) {
            ImmediatePostDominator ipd = postdoms.get(p);
            record(ipd, p);
        }
    }

    protected List<Edge<ImmediatePostDominator>> nonFatalPeers(
            Peer<ImmediatePostDominator> peer) {
        ExtTerm ext = (ExtTerm) AccrueExt_c.ext(peer.node());
        Set<EdgeIdentifier> catchSites =
                ext.getAnalysisResult(PreciseCallGraphFactory.CATCH_TARGETS,
                                      currentContext());
        if (catchSites == null) {
            catchSites = Collections.emptySet();
        }

        SubtypeSet caught = new SubtypeSet(ts.Throwable());
        for (EdgeIdentifier edge : catchSites) {
            EdgeType et = edge.getKey();
            if (et instanceof ExceptionEdge)
                caught.add(((ExceptionEdge) et).type());
            else throw new InternalCompilerError("Expected exception edge: "
                    + edge);
        }
        List<Edge<ImmediatePostDominator>> nonfatal =
                new ArrayList<Edge<ImmediatePostDominator>>();
        for (Edge<ImmediatePostDominator> edge : peer.preds()) {
            if (edge.getKey() instanceof ExceptionEdgeKey) {
                ExceptionEdgeKey k = (ExceptionEdgeKey) edge.getKey();
                if (caught.contains(k.type()))
                    nonfatal.add(edge);
                else {
                    if (Report.should_report(this.autil.workQueue()
                                                       .factory()
                                                       .reportTopics(), 4)) {
                        Report.report(4,
                                      "PostDominatorDataFlow: Excluding fatal edge "
                                              + edge);
                    }
                }
            }
            else nonfatal.add(edge);
        }
        return nonfatal;
    }
}
