/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 University of Maryland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import edu.umd.cs.findbugs.SystemProperties;

/**
 * Object to enumerate (some subset of) the simple paths in a CFG. A simple path
 * is a path from entry to exit, ignoring backedges and unhandled exceptions.
 * <p/>
 * <p>
 * FIXME: instead of storing the simple paths, should invoke a callback as each
 * simple path is produced. That would save memory.
 *
 * @author David Hovemeyer
 * @see CFG
 */
public class SimplePathEnumerator implements EdgeTypes, DFSEdgeTypes {
    private final CFG cfg;

    private final DepthFirstSearch dfs;

    private final int maxPaths;

    private final int maxWork;

    private int work;

    private final List<List<Edge>> pathList;

    private static final boolean DEBUG = SystemProperties.getBoolean("spe.debug");

    /**
     * Default number of steps to be performed in path enumeration.
     */
    public static final int DEFAULT_MAX_WORK = 200000;

    /**
     * Constructor.
     *
     * @param cfg
     *            the control flow graph to enumerate simple paths of
     * @param maxPaths
     *            maximum number of simple paths to find
     * @param maxWork
     *            maximum number of steps to be performed in the path
     *            enumeration (to handle exponential blowup of search space)
     */
    public SimplePathEnumerator(CFG cfg, int maxPaths, int maxWork) {
        this.cfg = cfg;
        this.dfs = new DepthFirstSearch(cfg);
        dfs.search();
        this.maxPaths = maxPaths;
        this.maxWork = maxWork;
        this.work = 0;
        this.pathList = new LinkedList<List<Edge>>();
    }

    /**
     * Constructor; max work is set to DEFAULT_MAX_WORK.
     *
     * @param cfg
     *            the control flow graph to enumerate simple paths of
     * @param maxPaths
     *            maximum number of simple paths to find
     */
    public SimplePathEnumerator(CFG cfg, int maxPaths) {
        this(cfg, maxPaths, DEFAULT_MAX_WORK);
    }

    /**
     * Enumerate the simple paths.
     *
     * @return this object
     */
    public SimplePathEnumerator enumerate() {
        Iterator<Edge> entryOut = cfg.outgoingEdgeIterator(cfg.getEntry());
        if (!entryOut.hasNext()) {
            throw new IllegalStateException();
        }
        Edge entryEdge = entryOut.next();

        LinkedList<Edge> init = new LinkedList<Edge>();
        init.add(entryEdge);

        work(init);
        if (DEBUG && work == maxWork) {
            System.out.println("**** Reached max work! ****");
        }

        return this;
    }

    /**
     * Iterate over simple paths.
     */
    public Iterator<List<Edge>> iterator() {
        return pathList.iterator();
    }

    private void work(LinkedList<Edge> partialPath) {
        if (pathList.size() == maxPaths) {
            return;
        }

        Edge last = partialPath.getLast();

        // Is this a complete path?
        if (last.getTarget() == cfg.getExit()) {
            pathList.add(new LinkedList<Edge>(partialPath));
            return;
        }

        // Look for non-backedge, non-unhandled-exception outgoing edges, and
        // recur.
        Iterator<Edge> i = cfg.outgoingEdgeIterator(last.getTarget());
        while (i.hasNext()) {
            Edge outEdge = i.next();

            // Ignore back edges and unhandled exception edges
            if (dfs.getDFSEdgeType(outEdge) == BACK_EDGE || outEdge.getType() == UNHANDLED_EXCEPTION_EDGE) {
                continue;
            }

            // Add the edge to the current partial path, and recur
            partialPath.add(outEdge);
            work(partialPath);
            partialPath.removeLast();

            // Have we done the maximum amount of work?
            if (work == maxWork) {
                return;
            }
            ++work;

            // Did we reach the maximum number of simple paths?
            if (pathList.size() == maxPaths) {
                return;
            }
        }
    }
}

