/*******************************************************************************
 * Copyright (c) 2003, 2010 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();
	}

}
