package com.jwetherell.algorithms.graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jwetherell.algorithms.data_structures.Graph;
import com.jwetherell.algorithms.data_structures.Graph.Edge;
import com.jwetherell.algorithms.data_structures.Graph.Vertex;

/**
 * In graph theory, a connected component (or just component) of an undirected graph is a subgraph in which any two vertices are connected to each 
 * other by paths, and which is connected to no additional vertices in the supergraph. A vertex with no incident edges is itself a connected 
 * component. A graph that is itself connected has exactly one connected component, consisting of the whole graph.
 * 
 * http://en.wikipedia.org/wiki/Connected_component_(graph_theory)
 * 
 * @author Justin Wetherell <phishman3579@gmail.com>
 */
public class ConnectedComponents {

    private ConnectedComponents() { }

    /**
     * Finds the connected components subsets of the Graph.
     * 
     * @param g Graph to find connected components.
     * @return List of connected components in the Graph.
     */
    public static final <T extends Comparable<T>> List<List<Vertex<T>>> getConnectedComponents(Graph<T> graph) {
        if (graph == null)
            throw new IllegalArgumentException("Graph is NULL.");

        if (graph.getType() != Graph.TYPE.DIRECTED)
            throw new IllegalArgumentException("Cannot perform a connected components search on a non-directed graph. graph type = "+graph.getType());

        final Map<Vertex<T>,Integer> map = new HashMap<Vertex<T>,Integer>();
        final List<List<Vertex<T>>> list = new ArrayList<List<Vertex<T>>>();

        int c = 0;
        for (Vertex<T> v : graph.getVertices()) 
            if (map.get(v) == null)
                visit(map, list, v, c++);
        return list;
    }

    private static final <T extends Comparable<T>> void visit(Map<Vertex<T>,Integer> map, List<List<Vertex<T>>> list, Vertex<T> v, int c) {
        map.put(v, c);

        List<Vertex<T>> r = null;
        if (c == list.size()) {
            r = new ArrayList<Vertex<T>>();
            list.add(r);
        } else {
            r = list.get(c);
        }
        r.add(v);

        if (v.getEdges().size() > 0) {
            boolean found = false;
            for (Edge<T> e : v.getEdges()) {
                final Vertex<T> to = e.getToVertex();
                if (map.get(to) == null) {
                    visit(map, list, to, c);
                    found = true;
                }
                if (found)
                    break;
            }
        }
    }
}
