/*******************************************************************************
 * Copyright (c) 2003, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d.graph;

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

/**
 * Performs a topological sort from left to right of the subgraphs in a compound directed
 * graph.  This ensures that subgraphs do not intertwine.
 * @author Randy Hudson
 * @since 2.1.2
 */
class SortSubgraphs extends GraphVisitor {

CompoundDirectedGraph g;

NestingTree nestingTrees[];

Set orderingGraphEdges = new HashSet();
Set orderingGraphNodes = new HashSet();
NodePair pair = new NodePair();

private void breakSubgraphCycles() {
	//The stack of nodes which have no unmarked incoming edges
	List noLefts = new ArrayList();

	int index = 1;
	//Identify all initial nodes for removal
	for (Iterator iter = orderingGraphNodes.iterator(); iter.hasNext();) {
		Node node = (Node)iter.next();
		if (node.x == 0)
			sortedInsert(noLefts, node);
	}
	
	Node cycleRoot;
	do {
		//Remove all leftmost nodes, updating the nodes to their right
		while (noLefts.size() > 0) {
			Node node = (Node)noLefts.remove(noLefts.size() - 1);
			node.sortValue = index++;
			orderingGraphNodes.remove(node);
//			System.out.println("removed:" + node);
			NodeList rightOf = rightOf(node);
			if (rightOf == null)
				continue;
			for (int i = 0; i < rightOf.size(); i++) {
				Node right = rightOf.getNode(i);
				right.x--;
				if (right.x == 0)
					sortedInsert(noLefts, right);
			}
		}
		cycleRoot = null;
		double min = Double.MAX_VALUE;
		for (Iterator iter = orderingGraphNodes.iterator(); iter.hasNext();) {
			Node node = (Node)iter.next();
			if (node.sortValue < min) {
				cycleRoot = node;
				min = node.sortValue;
			}
		}
		if (cycleRoot != null) {
			//break the cycle;
			sortedInsert(noLefts, cycleRoot);
//			System.out.println("breaking cycle with:" + cycleRoot);
//			Display.getCurrent().beep();
			cycleRoot.x = -1; //prevent x from ever reaching 0
		} // else if (OGmembers.size() > 0)
			//System.out.println("FAILED TO FIND CYCLE ROOT"); //$NON-NLS-1$
	} while (cycleRoot != null);
}

private void buildSubgraphOrderingGraph() {
	RankList ranks = g.ranks;
	nestingTrees = new NestingTree[ranks.size()];
	for (int r = 0; r < ranks.size(); r++) {
		NestingTree entry = NestingTree.buildNestingTreeForRank(ranks.getRank(r));
		nestingTrees[r] = entry;
		entry.calculateSortValues();
		entry.recursiveSort(false);
	}

	for (int i = 0; i < nestingTrees.length; i++) {
		NestingTree entry = nestingTrees[i];
		buildSubgraphOrderingGraph(entry);
	}
}

private void buildSubgraphOrderingGraph(NestingTree entry) {
	NodePair pair = new NodePair();
	if (entry.isLeaf)
		return;
	for (int i = 0; i < entry.contents.size(); i++) {
		Object right = entry.contents.get(i);
		if (right instanceof Node)
			pair.n2 = (Node)right;
		else {
			pair.n2 = ((NestingTree)right).subgraph;
			buildSubgraphOrderingGraph((NestingTree)right);
		}
		if (pair.n1 != null && !orderingGraphEdges.contains(pair)) {
			orderingGraphEdges.add(pair);
			leftToRight(pair.n1, pair.n2);
			orderingGraphNodes.add(pair.n1);
			orderingGraphNodes.add(pair.n2);
			pair.n2.x++; //Using x field to count predecessors.
			pair = new NodePair(pair.n2, null);
		} else {
			pair.n1 = pair.n2;
		}
	}
}

/**
 * Calculates the average position P for each node and subgraph.  The average position is
 * stored in the sortValue for each node or subgraph.
 * 
 * Runs in approximately linear time with respect to the number of nodes, including
 * virtual nodes.
 */
private void calculateSortValues() {
	RankList ranks = g.ranks;
	
	g.subgraphs.resetSortValues();
	g.subgraphs.resetIndices();
	
	/*
	 * For subgraphs, the sum of all positions is kept, along with the number of
	 * contributions, which is tracked in the subgraph's index field.
	 */
	for (int r = 0; r < ranks.size(); r++) {
		Rank rank = ranks.getRank(r);
		for (int j = 0; j < rank.count(); j++) {
			Node node = rank.getNode(j);
			node.sortValue = node.index;
			Subgraph parent = node.getParent();
			while (parent != null) {
				parent.sortValue += node.sortValue;
				parent.index++;
				parent = parent.getParent();
			}
		}
	}
	
	/*
	 * For each subgraph, divide the sum of the positions by the number of contributions,
	 * to give the average position.
	 */
	for (int i = 0; i < g.subgraphs.size(); i++) {
		Subgraph subgraph = (Subgraph)g.subgraphs.get(i);
		subgraph.sortValue /= subgraph.index;
	}
}

private void repopulateRanks() {
	for (int i = 0; i < nestingTrees.length; i++) {
		Rank rank = g.ranks.getRank(i);
		rank.clear();
		nestingTrees[i].repopulateRank(rank);
	}
}

private NodeList rightOf(Node left) {
	return (NodeList)left.workingData[0];
}

private void leftToRight(Node left, Node right) {
	rightOf(left).add(right);
}

void sortedInsert(List list, Node node) {
	int insert = 0;
	while (insert < list.size()
	  && ((Node)list.get(insert)).sortValue > node.sortValue)
		insert++;
	list.add(insert, node);
}

private void topologicalSort() {
	for (int i = 0; i < nestingTrees.length; i++) {
		nestingTrees[i].getSortValueFromSubgraph();
		nestingTrees[i].recursiveSort(false);
	}
}

void init() {
	for (int r = 0; r < g.ranks.size(); r++) {
		Rank rank = g.ranks.getRank(r);
		for (int i = 0; i < rank.count(); i++) {
			Node n = (Node)rank.get(i);
			n.workingData[0] = new NodeList();
		}
	}
	for (int i = 0; i < g.subgraphs.size(); i++) {
		Subgraph s = (Subgraph)g.subgraphs.get(i);
		s.workingData[0] = new NodeList();
	}
}

public void visit(DirectedGraph dg) {
	g = (CompoundDirectedGraph)dg;
	
	init();
	buildSubgraphOrderingGraph();
	calculateSortValues();
	breakSubgraphCycles();
	topologicalSort();
	repopulateRanks();
}

}
