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

}
