/*
* Copyright (c) 2003, The JUNG Authors
*
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* https://github.com/jrtom/jung/blob/master/LICENSE for a description.
*/
package edu.uci.ics.jung.algorithms.importance;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Stack;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedGraph;

/**
 * Computes betweenness centrality for each vertex and edge in the graph. The result is that each vertex
 * and edge has a UserData element of type MutableDouble whose key is 'centrality.BetweennessCentrality'.
 * Note: Many social network researchers like to normalize the betweenness values by dividing the values by
 * (n-1)(n-2)/2. The values given here are unnormalized.<p>
 *
 * A simple example of usage is:
 * <pre>
 * BetweennessCentrality ranker = new BetweennessCentrality(someGraph);
 * ranker.evaluate();
 * ranker.printRankings();
 * </pre>
 *
 * Running time is: O(n^2 + nm).
 * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001."
 * @author Scott White
 * @author Tom Nelson converted to jung2
 */

public class BetweennessCentrality<V,E> extends AbstractRanker<V,E> {

    public static final String CENTRALITY = "centrality.BetweennessCentrality";

    /**
     * Constructor which initializes the algorithm
     * @param g the graph whose nodes are to be analyzed
     */
    public BetweennessCentrality(Graph<V,E> g) {
        initialize(g, true, true);
    }

    public BetweennessCentrality(Graph<V,E> g, boolean rankNodes) {
        initialize(g, rankNodes, true);
    }

    public BetweennessCentrality(Graph<V,E> g, boolean rankNodes, boolean rankEdges)
    {
        initialize(g, rankNodes, rankEdges);
    }
    
	protected void computeBetweenness(Graph<V,E> graph) {

    	Map<V,BetweennessData> decorator = new HashMap<V,BetweennessData>();
    	Map<V,Number> bcVertexDecorator = 
    		vertexRankScores.getUnchecked(getRankScoreKey());
    	bcVertexDecorator.clear();
    	Map<E,Number> bcEdgeDecorator = 
    		edgeRankScores.getUnchecked(getRankScoreKey());
    	bcEdgeDecorator.clear();
        
        Collection<V> vertices = graph.getVertices();
        
        for (V s : vertices) {

            initializeData(graph,decorator);

            decorator.get(s).numSPs = 1;
            decorator.get(s).distance = 0;

            Stack<V> stack = new Stack<V>();
            Queue<V> queue = new LinkedList<V>();
            queue.add(s);

            while (!queue.isEmpty()) {
                V v = queue.remove();
                stack.push(v);

                for(V w : getGraph().getSuccessors(v)) {

                    if (decorator.get(w).distance < 0) {
                        queue.add(w);
                        decorator.get(w).distance = decorator.get(v).distance + 1;
                    }

                    if (decorator.get(w).distance == decorator.get(v).distance + 1) {
                        decorator.get(w).numSPs += decorator.get(v).numSPs;
                        decorator.get(w).predecessors.add(v);
                    }
                }
            }
            
            while (!stack.isEmpty()) {
                V w = stack.pop();

                for (V v : decorator.get(w).predecessors) {

                    double partialDependency = (decorator.get(v).numSPs / decorator.get(w).numSPs);
                    partialDependency *= (1.0 + decorator.get(w).dependency);
                    decorator.get(v).dependency +=  partialDependency;
                    E currentEdge = getGraph().findEdge(v, w);
                    double edgeValue = bcEdgeDecorator.get(currentEdge).doubleValue();
                    edgeValue += partialDependency;
                    bcEdgeDecorator.put(currentEdge, edgeValue);
                }
                if (w != s) {
                	double bcValue = bcVertexDecorator.get(w).doubleValue();
                	bcValue += decorator.get(w).dependency;
                	bcVertexDecorator.put(w, bcValue);
                }
            }
        }

        if(graph instanceof UndirectedGraph) {
            for (V v : vertices) { 
            	double bcValue = bcVertexDecorator.get(v).doubleValue();
            	bcValue /= 2.0;
            	bcVertexDecorator.put(v, bcValue);
            }
            for (E e : graph.getEdges()) {
            	double bcValue = bcEdgeDecorator.get(e).doubleValue();
            	bcValue /= 2.0;
            	bcEdgeDecorator.put(e, bcValue);
            }
        }

        for (V vertex : vertices) {
            decorator.remove(vertex);
        }
    }

    private void initializeData(Graph<V,E> g, Map<V,BetweennessData> decorator) {
        for (V vertex : g.getVertices()) {

        	Map<V,Number> bcVertexDecorator = vertexRankScores.getUnchecked(getRankScoreKey());
        	if(bcVertexDecorator.containsKey(vertex) == false) {
        		bcVertexDecorator.put(vertex, 0.0);
        	}
            decorator.put(vertex, new BetweennessData());
        }
        for (E e : g.getEdges()) {

        	Map<E,Number> bcEdgeDecorator = edgeRankScores.getUnchecked(getRankScoreKey());
        	if(bcEdgeDecorator.containsKey(e) == false) {
        		bcEdgeDecorator.put(e, 0.0);
        	}
        }
    }
    
    /**
     * the user datum key used to store the rank scores
     * @return the key
     */
    @Override
    public String getRankScoreKey() {
        return CENTRALITY;
    }

    @Override
    public void step() {
        computeBetweenness(getGraph());
    }

    class BetweennessData {
        double distance;
        double numSPs;
        List<V> predecessors;
        double dependency;

        BetweennessData() {
            distance = -1;
            numSPs = 0;
            predecessors = new ArrayList<V>();
            dependency = 0;
        }
    }
}
