/*******************************************************************************
 * 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.Iterator;
import java.util.Stack;

/**
 * Assigns the final rank assignment for a DirectedGraph with an initial
 * feasible spanning tree.
 * 
 * @author Randy Hudson
 * @since 2.1.2
 */
class RankAssignmentSolver extends SpanningTreeVisitor {

	DirectedGraph graph;
	EdgeList spanningTree;
	boolean searchDirection;

	int depthFirstCutValue(Edge edge, int count) {
		Node n = getTreeTail(edge);
		setTreeMin(n, count);
		int cutvalue = 0;
		int multiplier = (edge.target == n) ? 1 : -1;
		EdgeList list;

		list = n.outgoing;
		Edge e;
		for (int i = 0; i < list.size(); i++) {
			e = list.getEdge(i);
			if (e.tree && e != edge) {
				count = depthFirstCutValue(e, count);
				cutvalue += (e.cut - e.weight) * multiplier;
			} else {
				cutvalue -= e.weight * multiplier;
			}
		}
		list = n.incoming;
		for (int i = 0; i < list.size(); i++) {
			e = list.getEdge(i);
			if (e.tree && e != edge) {
				count = depthFirstCutValue(e, count);
				cutvalue -= (e.cut - e.weight) * multiplier;
			} else {
				cutvalue += e.weight * multiplier;
			}
		}

		edge.cut = cutvalue;
		if (cutvalue < 0)
			spanningTree.add(edge);
		setTreeMax(n, count);
		return count + 1;
	}

	/**
	 * returns the Edge which should be entered.
	 * 
	 * @param branch
	 * @return Edge
	 */
	Edge enter(Node branch) {
		Node n;
		Edge result = null;
		int minSlack = Integer.MAX_VALUE;
		boolean incoming = getParentEdge(branch).target != branch;
		// searchDirection = !searchDirection;
		for (int i = 0; i < graph.nodes.size(); i++) {
			if (searchDirection)
				n = graph.nodes.getNode(i);
			else
				n = graph.nodes.getNode(graph.nodes.size() - 1 - i);
			if (subtreeContains(branch, n)) {
				EdgeList edges;
				if (incoming)
					edges = n.incoming;
				else
					edges = n.outgoing;
				for (int j = 0; j < edges.size(); j++) {
					Edge e = edges.getEdge(j);
					if (!subtreeContains(branch, e.opposite(n)) && !e.tree
							&& e.getSlack() < minSlack) {
						result = e;
						minSlack = e.getSlack();
					}
				}
			}
		}
		return result;
	}

	int getTreeMax(Node n) {
		return n.workingInts[1];
	}

	int getTreeMin(Node n) {
		return n.workingInts[0];
	}

	void initCutValues() {
		Node root = graph.nodes.getNode(0);
		spanningTree = new EdgeList();
		Edge e;
		setTreeMin(root, 1);
		setTreeMax(root, 1);

		for (int i = 0; i < root.outgoing.size(); i++) {
			e = root.outgoing.getEdge(i);
			if (!getSpanningTreeChildren(root).contains(e))
				continue;
			setTreeMax(root, depthFirstCutValue(e, getTreeMax(root)));
		}
		for (int i = 0; i < root.incoming.size(); i++) {
			e = root.incoming.getEdge(i);
			if (!getSpanningTreeChildren(root).contains(e))
				continue;
			setTreeMax(root, depthFirstCutValue(e, getTreeMax(root)));
		}
	}

	Edge leave() {
		Edge result = null;
		Edge e;
		int minCut = 0;
		int weight = -1;
		for (int i = 0; i < spanningTree.size(); i++) {
			e = spanningTree.getEdge(i);
			if (e.cut < minCut) {
				result = e;
				minCut = result.cut;
				weight = result.weight;
			} else if (e.cut == minCut && e.weight > weight) {
				result = e;
				weight = result.weight;
			}
		}
		return result;
	}

	void networkSimplexLoop() {
		Edge leave, enter;
		int count = 0;
		while ((leave = leave()) != null && count < 900) {

			count++;

			Node leaveTail = getTreeTail(leave);
			Node leaveHead = getTreeHead(leave);

			enter = enter(leaveTail);
			if (enter == null)
				break;

			// Break the "leave" edge from the spanning tree
			getSpanningTreeChildren(leaveHead).remove(leave);
			setParentEdge(leaveTail, null);
			leave.tree = false;
			spanningTree.remove(leave);

			Node enterTail = enter.source;
			if (!subtreeContains(leaveTail, enterTail))
				// Oops, wrong end of the edge
				enterTail = enter.target;
			Node enterHead = enter.opposite(enterTail);

			// Prepare enterTail by making it the root of its sub-tree
			updateSubgraph(enterTail);

			// Add "enter" edge to the spanning tree
			getSpanningTreeChildren(enterHead).add(enter);
			setParentEdge(enterTail, enter);
			enter.tree = true;

			repairCutValues(enter);

			Node commonAncestor = enterHead;

			while (!subtreeContains(commonAncestor, leaveHead)) {
				repairCutValues(getParentEdge(commonAncestor));
				commonAncestor = getTreeParent(commonAncestor);
			}
			while (leaveHead != commonAncestor) {
				repairCutValues(getParentEdge(leaveHead));
				leaveHead = getTreeParent(leaveHead);
			}
			updateMinMax(commonAncestor, getTreeMin(commonAncestor));
			tightenEdge(enter);
		}
	}

	void repairCutValues(Edge edge) {
		spanningTree.remove(edge);
		Node n = getTreeTail(edge);
		int cutvalue = 0;
		int multiplier = (edge.target == n) ? 1 : -1;
		EdgeList list;

		list = n.outgoing;
		Edge e;
		for (int i = 0; i < list.size(); i++) {
			e = list.getEdge(i);
			if (e.tree && e != edge)
				cutvalue += (e.cut - e.weight) * multiplier;
			else
				cutvalue -= e.weight * multiplier;
		}
		list = n.incoming;
		for (int i = 0; i < list.size(); i++) {
			e = list.getEdge(i);
			if (e.tree && e != edge)
				cutvalue -= (e.cut - e.weight) * multiplier;
			else
				cutvalue += e.weight * multiplier;
		}

		edge.cut = cutvalue;
		if (cutvalue < 0)
			spanningTree.add(edge);
	}

	void setTreeMax(Node n, int value) {
		n.workingInts[1] = value;
	}

	void setTreeMin(Node n, int value) {
		n.workingInts[0] = value;
	}

	boolean subtreeContains(Node parent, Node child) {
		return parent.workingInts[0] <= child.workingInts[1]
				&& child.workingInts[1] <= parent.workingInts[1];
	}

	void tightenEdge(Edge edge) {
		Node tail = getTreeTail(edge);
		int delta = edge.getSlack();
		if (tail == edge.target)
			delta = -delta;
		Node n;
		for (int i = 0; i < graph.nodes.size(); i++) {
			n = graph.nodes.getNode(i);
			if (subtreeContains(tail, n))
				n.rank += delta;
		}
	}

	int updateMinMax(Node root, int count) {
		setTreeMin(root, count);
		EdgeList edges = getSpanningTreeChildren(root);
		for (int i = 0; i < edges.size(); i++)
			count = updateMinMax(getTreeTail(edges.getEdge(i)), count);
		setTreeMax(root, count);
		return count + 1;
	}

	void updateSubgraph(Node root) {
		Edge flip = getParentEdge(root);
		if (flip != null) {
			Node rootParent = getTreeParent(root);
			getSpanningTreeChildren(rootParent).remove(flip);
			updateSubgraph(rootParent);
			setParentEdge(root, null);
			setParentEdge(rootParent, flip);
			repairCutValues(flip);
			getSpanningTreeChildren(root).add(flip);
		}
	}

	public void visit(DirectedGraph graph) {
		this.graph = graph;
		initCutValues();
		networkSimplexLoop();
		if (graph.forestRoot == null)
			graph.nodes.normalizeRanks();
		else
			normalizeForest();
	}

	private void normalizeForest() {
		NodeList tree = new NodeList();
		graph.nodes.resetFlags();
		graph.forestRoot.flag = true;
		EdgeList rootEdges = graph.forestRoot.outgoing;
		Stack stack = new Stack();
		for (int i = 0; i < rootEdges.size(); i++) {
			Node node = rootEdges.getEdge(i).target;
			node.flag = true;
			stack.push(node);
			while (!stack.isEmpty()) {
				node = (Node) stack.pop();
				tree.add(node);
				Iterator neighbors = node.iteratorNeighbors();
				while (neighbors.hasNext()) {
					Node neighbor = (Node) neighbors.next();
					if (!neighbor.flag) {
						neighbor.flag = true;
						stack.push(neighbor);
					}
				}
			}
			tree.normalizeRanks();
			tree.clear();
		}
	}

}
