/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University 
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.shortestpath;

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

import edu.uci.ics.jung.graph.Hypergraph;

/**
 * Labels each node in the graph according to the BFS distance from the start
 * node(s). If nodes are unreachable, then they are assigned a distance of -1.
 * All nodes traversed at step k are marked as predecessors of their successors
 * traversed at step k+1.
 * <p>
 * Running time is: O(m)
 * 
 * @author Scott White
 */
public class BFSDistanceLabeler<V, E> {

	private Map<V, Number> distanceDecorator = new HashMap<V, Number>();
	private List<V> mCurrentList;
	private Set<V> mUnvisitedVertices;
	private List<V> mVerticesInOrderVisited;
	private Map<V, HashSet<V>> mPredecessorMap;

	/**
	 * Creates a new BFS labeler for the specified graph and root set The
	 * distances are stored in the corresponding Vertex objects and are of type
	 * MutableInteger
	 */
	public BFSDistanceLabeler() {
		mPredecessorMap = new HashMap<V, HashSet<V>>();
	}

	/**
	 * Returns the list of vertices visited in order of traversal
	 * 
	 * @return the list of vertices
	 */
	public List<V> getVerticesInOrderVisited() {
		return mVerticesInOrderVisited;
	}

	/**
	 * Returns the set of all vertices that were not visited
	 * 
	 * @return the list of unvisited vertices
	 */
	public Set<V> getUnvisitedVertices() {
		return mUnvisitedVertices;
	}

	/**
	 * Given a vertex, returns the shortest distance from any node in the root
	 * set to v
	 * 
	 * @param v
	 *            the vertex whose distance is to be retrieved
	 * @return the shortest distance from any node in the root set to v
	 */
	public int getDistance(Hypergraph<V, E> g, V v) {
		if (!g.getVertices().contains(v)) {
			throw new IllegalArgumentException(
					"Vertex is not contained in the graph.");
		}

		return distanceDecorator.get(v).intValue();
	}

	/**
	 * Returns set of predecessors of the given vertex
	 * 
	 * @param v
	 *            the vertex whose predecessors are to be retrieved
	 * @return the set of predecessors
	 */
	public Set<V> getPredecessors(V v) {
		return mPredecessorMap.get(v);
	}

	protected void initialize(Hypergraph<V, E> g, Set<V> rootSet) {
		mVerticesInOrderVisited = new ArrayList<V>();
		mUnvisitedVertices = new HashSet<V>();
		for (V currentVertex : g.getVertices()) {
			mUnvisitedVertices.add(currentVertex);
			mPredecessorMap.put(currentVertex, new HashSet<V>());
		}

		mCurrentList = new ArrayList<V>();
		for (V v : rootSet) {
			distanceDecorator.put(v, Integer.valueOf(0));
			mCurrentList.add(v);
			mUnvisitedVertices.remove(v);
			mVerticesInOrderVisited.add(v);
		}
	}

	private void addPredecessor(V predecessor, V sucessor) {
		HashSet<V> predecessors = mPredecessorMap.get(sucessor);
		predecessors.add(predecessor);
	}

	/**
	 * Computes the distances of all the node from the starting root nodes. If
	 * there is more than one root node the minimum distance from each root node
	 * is used as the designated distance to a given node. Also keeps track of
	 * the predecessors of each node traversed as well as the order of nodes
	 * traversed.
	 * 
	 * @param graph
	 *            the graph to label
	 * @param rootSet
	 *            the set of starting vertices to traverse from
	 */
	public void labelDistances(Hypergraph<V, E> graph, Set<V> rootSet) {

		initialize(graph, rootSet);

		int distance = 1;
		while (true) {
			List<V> newList = new ArrayList<V>();
			for (V currentVertex : mCurrentList) {
				if (graph.containsVertex(currentVertex)) {
					for (V next : graph.getSuccessors(currentVertex)) {
						visitNewVertex(currentVertex, next, distance, newList);
					}
				}
			}
			if (newList.size() == 0) {
				break;
			}
			mCurrentList = newList;
			distance++;
		}

		for (V v : mUnvisitedVertices) {
			distanceDecorator.put(v, Integer.valueOf(-1));
		}
	}

	/**
	 * Computes the distances of all the node from the specified root node. Also
	 * keeps track of the predecessors of each node traversed as well as the
	 * order of nodes traversed.
	 * 
	 * @param graph
	 *            the graph to label
	 * @param root
	 *            the single starting vertex to traverse from
	 */
	public void labelDistances(Hypergraph<V, E> graph, V root) {
		labelDistances(graph, Collections.singleton(root));
	}

	private void visitNewVertex(V predecessor, V neighbor, int distance,
			List<V> newList) {
		if (mUnvisitedVertices.contains(neighbor)) {
			distanceDecorator.put(neighbor, Integer.valueOf(distance));
			newList.add(neighbor);
			mVerticesInOrderVisited.add(neighbor);
			mUnvisitedVertices.remove(neighbor);
		}
		int predecessorDistance = distanceDecorator.get(predecessor).intValue();
		int successorDistance = distanceDecorator.get(neighbor).intValue();
		if (predecessorDistance < successorDistance) {
			addPredecessor(predecessor, neighbor);
		}
	}

	/**
	 * Returns a map from vertices to minimum distances from the original
	 * source(s). Must be called after {@code labelDistances} in order to
	 * contain valid data.
	 */
	public Map<V, Number> getDistanceDecorator() {
		return distanceDecorator;
	}
}
