/*
 *    GeoTools - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2003-2006, GeoTools Project Managment Committee (PMC)
 *    (C) 2003, Refractions Reserach Inc.
 *        
 *    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;
 *    version 2.1 of the License.
 *
 *    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.
 */
package org.geotools.graph.util.graph;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import org.geotools.graph.structure.Graph;
import org.geotools.graph.structure.Graphable;
import org.geotools.graph.structure.Node;
import org.geotools.graph.structure.basic.BasicGraph;
import org.geotools.graph.traverse.GraphTraversal;
import org.geotools.graph.traverse.GraphWalker;
import org.geotools.graph.traverse.basic.BasicGraphTraversal;
import org.geotools.graph.traverse.standard.DepthFirstIterator;

/**
 * Creates a collection of connected graphs from a single graph. A 
 * connected graph in which for every two pair of nodes, there is a path between
 * them.  
 * 
 * @author Justin Deoliveira, Refractions Research Inc, jdeolive@refractions.net
 *
 * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/graph/src/main/java/org/geotools/graph/util/graph/GraphPartitioner.java $
 */
public class GraphPartitioner implements GraphWalker {

  /** graph to be partitioned into connected components **/
  private Graph m_graph;
  
  /** paritions of graph **/
  private ArrayList m_partitions;
  
  /** current partition **/
  private ArrayList m_partition;
  
  /** visited counter **/
  private int m_nvisited;
  
  /**
   * Constructs a new partitioner for a graph.
   *  
   * @param graph Graph to be partitioned.
   */
  public GraphPartitioner(Graph graph) {
    m_graph = graph;
    m_partitions = new ArrayList();
  }
  
  /**
   * Performs the partition.
   * 
   * @return True if the partition was successful, otherwise false.
   */
  public boolean partition() {
    //strategy is to perform a depth first search from a node, every node is 
    // reaches is connected therefore in the same partition
    // when traversal ends, start from a new source, repeat until no more 
    // sources
    
    try {
      m_nvisited = m_graph.getNodes().size();
      
      DepthFirstIterator iterator = new DepthFirstIterator();
      BasicGraphTraversal traversal = new BasicGraphTraversal(
        m_graph, this, iterator
      );
      
      Iterator sources = m_graph.getNodes().iterator();
      
      traversal.init();
      m_partition = new ArrayList();
      
      while(m_nvisited > 0) {
        
        //find a node that hasn't been visited and set as source of traversal
        Node source = null;
        while(sources.hasNext() && (source = (Node)sources.next()).isVisited());
       
        //if we could not find a source, return false
        if (source == null || source.isVisited()) return(false);
        
        iterator.setSource(source);
        traversal.traverse();
      }
      
      //create the individual graphs
      HashSet nodes = null;
      HashSet edges = null;
      ArrayList graphs = new ArrayList();
      
      for (Iterator itr = m_partitions.iterator(); itr.hasNext();) {
        m_partition = (ArrayList)itr.next();
        if (m_partition.size() == 0) continue;
        
        nodes = new HashSet();
        edges = new HashSet();
        for (Iterator nitr = m_partition.iterator(); nitr.hasNext();) {
          Node node = (Node)nitr.next();
          nodes.add(node);
          edges.addAll(node.getEdges());
        } 
        
        graphs.add(new BasicGraph(nodes, edges));
      }
      
      m_partitions = graphs;
      
      return(true);
    }
    catch(Exception e) {
      e.printStackTrace();  
    }
    return(false);
  }
  
  /**
   * Returns the partitions of the graph.
   * 
   * @return A collection of Graph objects.
   * 
   * @see Graph
   */
  public List getPartitions() {
    return(m_partitions);  
  }
  
  /**
   * Adds the element to the current partition.
   * 
   * @see GraphWalker#visit(Graphable, GraphTraversal)
   */
  public int visit(Graphable element, GraphTraversal traversal) {
    //add element to current set
    m_nvisited--;
    m_partition.add(element);
    return(GraphTraversal.CONTINUE);
  }

  /**
   * Adds the current partition to the completed set of partitions and 
   * creates a new partition.
   * 
   * @see GraphWalker#finish()
   */
  public void finish() {
    //create a new set
    m_partitions.add(m_partition);
    m_partition = new ArrayList();
  }
}
