/*
 * Generic graph library
 * 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.graph;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Perform a depth first search on a graph. Algorithm based on Cormen, et. al,
 * <cite>Introduction to Algorithms</cite>, p. 478. Currently, this class
 * <ul>
 * <li>assigns DFS edge types (see {@link DFSEdgeTypes})
 * <li>assigns discovery and finish times for each vertex
 * <li>produces a topological sort of the vertices,
 * <em>if and only if the graph is acyclic</em>
 * </ul>
 * 
 * <p>
 * Concrete subclasses implement forward and reverse versions of depth first
 * search.
 * 
 * @author David Hovemeyer
 * @see Graph
 * @see DepthFirstSearch
 * @see ReverseDepthFirstSearch
 */
public abstract class AbstractDepthFirstSearch<GraphType extends Graph<EdgeType, VertexType>, EdgeType extends GraphEdge<EdgeType, VertexType>, VertexType extends GraphVertex<VertexType>>
        implements DFSEdgeTypes {

    public final static boolean DEBUG = false;

    private GraphType graph;

    private int[] colorList;

    private int[] discoveryTimeList;

    private int[] finishTimeList;

    private int[] dfsEdgeTypeList;

    private int timestamp;

    private LinkedList<VertexType> topologicalSortList;

    private VertexChooser<VertexType> vertexChooser;

    private SearchTreeCallback<VertexType> searchTreeCallback;

    /**
     * Color of a vertex which hasn't been visited yet.
     */
    protected static final int WHITE = 0;

    /**
     * Color of a vertex which has been visited, but not all of whose
     * descendents have been visited.
     */
    protected static final int GRAY = 1;

    /**
     * Color of a vertex whose entire search tree has been visited.
     */
    protected static final int BLACK = 2;

    /**
     * Constructor.
     * 
     * @param graph
     *            the graph to be searched
     * @throws IllegalArgumentException
     *             if the graph has not had edge ids assigned yet
     */
    public AbstractDepthFirstSearch(GraphType graph) {
        this.graph = graph;

        int numBlocks = graph.getNumVertexLabels();
        colorList = new int[numBlocks]; // initially all elements are WHITE
        discoveryTimeList = new int[numBlocks];
        finishTimeList = new int[numBlocks];

        int maxEdgeId = graph.getNumEdgeLabels();
        dfsEdgeTypeList = new int[maxEdgeId];

        timestamp = 0;

        topologicalSortList = new LinkedList<VertexType>();
    }

    // Abstract methods allow the concrete subclass to define
    // the "polarity" of the depth first search. That way,
    // this code can do normal DFS, or DFS of reversed GraphType.

    /**
     * Get Iterator over "logical" outgoing edges.
     */
    protected abstract Iterator<EdgeType> outgoingEdgeIterator(GraphType graph, VertexType vertex);

    /**
     * Get "logical" target of edge.
     */
    protected abstract VertexType getTarget(EdgeType edge);

    /**
     * Get "logical" source of edge.
     */
    protected abstract VertexType getSource(EdgeType edge);

    /**
     * Choose the next search tree root. By default, this method just scans for
     * a WHITE vertex. Subclasses may override this method in order to choose
     * which vertices are used as search tree roots.
     * 
     * @return the next search tree root
     */
    protected VertexType getNextSearchTreeRoot() {
        // FIXME: slow linear search, should improve
        for (Iterator<VertexType> i = graph.vertexIterator(); i.hasNext();) {
            VertexType vertex = i.next();
            if (visitMe(vertex))
                return vertex;
        }
        return null;
    }

    public Collection<VertexType> unvisitedVertices() {
        LinkedList<VertexType> result = new LinkedList<VertexType>();

        for (Iterator<VertexType> i = graph.vertexIterator(); i.hasNext();) {
            VertexType v = i.next();
            if (getColor(v) == WHITE)
                result.add(v);
        }
        return result;
    }

    /**
     * Specify a VertexChooser object to be used to selected which vertices are
     * visited by the search. This is useful if you only want to search a subset
     * of the vertices in the graph.
     * 
     * @param vertexChooser
     *            the VertexChooser to use
     */
    public void setVertexChooser(VertexChooser<VertexType> vertexChooser) {
        this.vertexChooser = vertexChooser;
    }

    /**
     * Set a search tree callback.
     * 
     * @param searchTreeCallback
     *            the search tree callback
     */
    public void setSearchTreeCallback(SearchTreeCallback<VertexType> searchTreeCallback) {
        this.searchTreeCallback = searchTreeCallback;
    }

    /**
     * Perform the depth first search.
     * 
     * @return this object
     */
    public AbstractDepthFirstSearch<GraphType, EdgeType, VertexType> search() {
        visitAll();
        classifyUnknownEdges();

        return this;
    }

    /**
     * Return whether or not the graph contains a cycle: i.e., whether it
     * contains any back edges. This should only be called after search() has
     * been called (since that method actually executes the search).
     * 
     * @return true if the graph contains a cycle, false otherwise
     */
    public boolean containsCycle() {
        for (Iterator<EdgeType> i = graph.edgeIterator(); i.hasNext();) {
            EdgeType edge = i.next();
            if (getDFSEdgeType(edge) == BACK_EDGE)
                return true;
        }
        return false;
    }

    /**
     * Get the type of edge in the depth first search.
     * 
     * @param edge
     *            the edge
     * @return the DFS type of edge: TREE_EDGE, FORWARD_EDGE, CROSS_EDGE, or
     *         BACK_EDGE
     * @see DFSEdgeTypes
     */
    public int getDFSEdgeType(EdgeType edge) {
        return dfsEdgeTypeList[edge.getLabel()];
    }

    /**
     * Return the timestamp indicating when the given vertex was discovered.
     * 
     * @param vertex
     *            the vertex
     */
    public int getDiscoveryTime(VertexType vertex) {
        return discoveryTimeList[vertex.getLabel()];
    }

    /**
     * Return the timestamp indicating when the given vertex was finished
     * (meaning that all of its descendents were visited recursively).
     * 
     * @param vertex
     *            the vertex
     */
    public int getFinishTime(VertexType vertex) {
        return finishTimeList[vertex.getLabel()];
    }

    /**
     * Get array of finish times, indexed by vertex label.
     * 
     * @return the array of finish times
     */
    @SuppressFBWarnings("EI")
    public int[] getFinishTimeList() {
        return finishTimeList;
    }

    /**
     * Get an iterator over the vertexs in topological sort order.
     * <em>This works if and only if the graph is acyclic.</em>
     */
    public Iterator<VertexType> topologicalSortIterator() {
        return topologicalSortList.iterator();
    }

    private class Visit {
        private VertexType vertex;

        private Iterator<EdgeType> outgoingEdgeIterator;

        public Visit(VertexType vertex) {
            if (vertex == null)
                throw new IllegalStateException();
            this.vertex = vertex;
            this.outgoingEdgeIterator = outgoingEdgeIterator(graph, vertex);

            // Mark the vertex as visited, and set its timestamp
            setColor(vertex, GRAY);
            setDiscoveryTime(vertex, timestamp++);
        }

        public VertexType getVertex() {
            return vertex;
        }

        public boolean hasNextEdge() {
            return outgoingEdgeIterator.hasNext();
        }

        public EdgeType getNextEdge() {
            return outgoingEdgeIterator.next();
        }
    }

    private void visitAll() {
        for (;;) {
            VertexType searchTreeRoot = getNextSearchTreeRoot();
            if (searchTreeRoot == null)
                // No more work to do
                break;

            if (searchTreeCallback != null)
                searchTreeCallback.startSearchTree(searchTreeRoot);

            ArrayList<Visit> stack = new ArrayList<Visit>(graph.getNumVertexLabels());
            stack.add(new Visit(searchTreeRoot));

            while (!stack.isEmpty()) {
                Visit visit = stack.get(stack.size() - 1);

                if (visit.hasNextEdge()) {
                    // Continue visiting successors
                    EdgeType edge = visit.getNextEdge();
                    visitSuccessor(stack, edge);
                } else {
                    // Finish the vertex
                    VertexType vertex = visit.getVertex();
                    setColor(vertex, BLACK);
                    topologicalSortList.addFirst(vertex);
                    setFinishTime(vertex, timestamp++);

                    stack.remove(stack.size() - 1);
                }
            }
        }
    }

    private void visitSuccessor(ArrayList<Visit> stack, EdgeType edge) {
        // Get the successor
        VertexType succ = getTarget(edge);
        int succColor = getColor(succ);

        // Classify edge type (if possible)
        int dfsEdgeType = 0;
        switch (succColor) {
        case WHITE:
            dfsEdgeType = TREE_EDGE;
            break;
        case GRAY:
            dfsEdgeType = BACK_EDGE;
            break;
        case BLACK:
            dfsEdgeType = UNKNOWN_EDGE;
            break;// We can't distinguish between CROSS and FORWARD edges at
                  // this point
        default:
            assert false;
        }
        setDFSEdgeType(edge, dfsEdgeType);

        // If successor hasn't been visited yet, visit it
        if (visitMe(succ)) {
            // Add to search tree (if a search tree callback exists)
            if (searchTreeCallback != null)
                searchTreeCallback.addToSearchTree(getSource(edge), getTarget(edge));

            // Add to visitation stack
            stack.add(new Visit(succ));
        }
    }

    // Classify CROSS and FORWARD edges
    private void classifyUnknownEdges() {
        Iterator<EdgeType> edgeIter = graph.edgeIterator();
        while (edgeIter.hasNext()) {
            EdgeType edge = edgeIter.next();
            int dfsEdgeType = getDFSEdgeType(edge);
            if (dfsEdgeType == UNKNOWN_EDGE) {
                int srcDiscoveryTime = getDiscoveryTime(getSource(edge));
                int destDiscoveryTime = getDiscoveryTime(getTarget(edge));

                if (srcDiscoveryTime < destDiscoveryTime) {
                    // If the source was visited earlier than the
                    // target, it's a forward edge.
                    dfsEdgeType = FORWARD_EDGE;
                } else {
                    // If the source was visited later than the
                    // target, it's a cross edge.
                    dfsEdgeType = CROSS_EDGE;
                }

                setDFSEdgeType(edge, dfsEdgeType);
            }
        }
    }

    private void setColor(VertexType vertex, int color) {
        colorList[vertex.getLabel()] = color;
    }

    /**
     * Get the current color of given vertex.
     * 
     * @param vertex
     *            the vertex
     * @return the color (WHITE, BLACK, or GRAY)
     */
    protected int getColor(VertexType vertex) {
        return colorList[vertex.getLabel()];
    }

    /**
     * Predicate to determine which vertices should be visited as the search
     * progresses. Takes both vertex color and the vertex chooser (if any) into
     * account.
     * 
     * @param vertex
     *            the vertex to possibly be visited
     * @return true if the vertex should be visited, false if not
     */
    protected boolean visitMe(VertexType vertex) {
        return (getColor(vertex) == WHITE) && (vertexChooser == null || vertexChooser.isChosen(vertex));
    }

    private void setDiscoveryTime(VertexType vertex, int ts) {
        discoveryTimeList[vertex.getLabel()] = ts;
    }

    private void setFinishTime(VertexType vertex, int ts) {
        finishTimeList[vertex.getLabel()] = ts;
    }

    private void setDFSEdgeType(EdgeType edge, int dfsEdgeType) {
        dfsEdgeTypeList[edge.getLabel()] = dfsEdgeType;
    }
}

// vim:ts=4
