/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.hibernate.eclipse.graph.layout;

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

import org.eclipse.draw2d.graph.DirectedGraph;
import org.eclipse.draw2d.graph.Edge;
import org.eclipse.draw2d.graph.EdgeList;
import org.eclipse.draw2d.graph.Node;
import org.eclipse.draw2d.graph.NodeList;

/**
 * Creates dummy edges between nodes, to be used with NodeJoiningDirectedGraphLayout
 * @author Phil Zoio
 */
public class ClusterEdgeCreator
{

	//sets up maximum depth of recursion to set up initial cluster list
	private static final int INITIAL_RECURSION_DEPTH = 3;

	NodeList nodeList;
	EdgeList edgeList;
	DirectedGraph graph;

	List<Node> encountered = new ArrayList<Node>();
	List<Cluster> clusters = new ArrayList<Cluster>();

	Cluster currentCluster = null;

	/**
	 * @param graph
	 */
	public ClusterEdgeCreator()
	{
		super();
	}

	public void visit(DirectedGraph graph)
	{

		try
		{

			this.graph = graph;
			this.nodeList = graph.nodes;
			this.edgeList = graph.edges;

			//iterate through all of the nodes in the node list
			for (Iterator<Node> iter = nodeList.iterator(); iter.hasNext();)
			{
				Node node = iter.next();

				//check whether we have already come across this node
				if (!encountered.contains(node))
				{
					//create a new cluster for this node
					currentCluster = new Cluster();
					clusters.add(currentCluster);
					encountered.add(node);
					currentCluster.set.add(node);

					//System.out.println("Adding to NEW cluster: " + node + ", cluster: " + currentCluster);
					// recursively add any other nodes reachable from it
					int depth = INITIAL_RECURSION_DEPTH;
					recursivelyAddToCluster(node, depth);
				}
				else
				{
					//System.out.println("Already encountered: " + node);
				}
			}

			/*System.out.println("Clusters: ");
			for (Iterator<Cluster> iter = clusters.iterator(); iter.hasNext();)
			{
				Cluster cluster = iter.next();
				System.out.println(cluster);

			}*/

			coalesceRemainingClusters();

			//System.out.println("");
			joinClusters();

		}
		catch (RuntimeException e)
		{
			e.printStackTrace();
			throw e;
		}

	}

	/**
	 * If recursion fails to join all the remaining
	 */
	private void coalesceRemainingClusters()
	{
	}

	/**
	 * Joins the clusters together
	 */
	private void joinClusters()
	{
		if (clusters.size() > 1)
		{

			Node sourceNode = null;
			Node targetNode = null;

			//add an edge from each successive cluster to next
			for (Iterator<Cluster> iter = clusters.iterator(); iter.hasNext();)
			{
				Cluster cluster = iter.next();
				if (sourceNode != null)
				{
					//use first node in set as target node
					targetNode = cluster.set.get(0);
					newDummyEdge(sourceNode, targetNode);
				}

				//set up source node for the next iteration using last node in
				// set
				sourceNode = cluster.set.get(cluster.set.size() - 1);

			}

		}
	}

	
	private void recursivelyAddToCluster(Node node, int depth)
	{

		if (depth > 3)
			return;
		else
		{
			depth++;
			EdgeList incoming = node.incoming;
			for (Iterator<Edge> iter = incoming.iterator(); iter.hasNext();)
			{
				Edge edge = iter.next();
				Node incomingNode = edge.source;

				if (!encountered.contains(incomingNode))
				{
					encountered.add(incomingNode);
					currentCluster.set.add(incomingNode);
					//System.out.println("Adding to current cluster: " + incomingNode + ", cluster: " + currentCluster);
					recursivelyAddToCluster(incomingNode, depth);
				}
				else
				{
					//System.out.println("Already encountered: " + incomingNode);
				}
			}

			EdgeList outgoing = node.outgoing;
			for (Iterator iter = outgoing.iterator(); iter.hasNext();)
			{
				Edge edge = (Edge) iter.next();
				Node outgoingNode = edge.target;

				if (!encountered.contains(outgoingNode))
				{
					encountered.add(outgoingNode);
					currentCluster.set.add(outgoingNode);
					//System.out.println("Adding to current cluster: " + outgoingNode + ", cluster: " + currentCluster);
					recursivelyAddToCluster(outgoingNode, depth);
				}
				else
				{
					//System.out.println("Already encountered: " + outgoingNode);
				}
			}
		}

	}

	/**
	 * creates a new dummy edge to be used in the graph
	 */
	private Edge newDummyEdge(Node sourceNode, Node targetNode)
	{
		boolean addedEdge;
		DummyEdgePart edgePart = new DummyEdgePart();
		Edge edge = new Edge(edgePart, sourceNode, targetNode);
		edge.weight = 2;

		//add the new edge to the edge list
		edgeList.add(edge);

		//targetNode = sourceNode;
		addedEdge = true;
		return edge;
	}

	/**
	 * Very thin wrapper around List
	 */
	static private class Cluster
	{

		List<Node> set = new ArrayList<Node>();

		public String toString()
		{
			return set.toString();
		}
	}

}