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

/**
 * Sorts nodes in a compound directed graph.
 * 
 * @author Randy Hudson
 * @since 2.1.2
 */
class CompoundRankSorter extends RankSorter {

	static class RowEntry {
		double contribution;
		int count;

		void reset() {
			count = 0;
			contribution = 0;
		}
	}

	static class RowKey {
		int rank;
		Subgraph s;

		RowKey() {
		}

		RowKey(Subgraph s, int rank) {
			this.s = s;
			this.rank = rank;
		}

		public boolean equals(Object obj) {
			RowKey rp = (RowKey) obj;
			return rp.s == s && rp.rank == rank;
		}

		public int hashCode() {
			return s.hashCode() ^ (rank * 31);
		}
	}

	boolean init;
	RowKey key = new RowKey();

	Map map = new HashMap();

	void addRowEntry(Subgraph s, int row) {
		key.s = s;
		key.rank = row;
		if (!map.containsKey(key))
			map.put(new RowKey(s, row), new RowEntry());
	}

	protected void assignIncomingSortValues() {
		super.assignIncomingSortValues();
	}

	protected void assignOutgoingSortValues() {
		super.assignOutgoingSortValues();
	}

	void optimize(DirectedGraph g) {
		CompoundDirectedGraph graph = (CompoundDirectedGraph) g;
		Iterator containment = graph.containment.iterator();
		while (containment.hasNext())
			graph.removeEdge((Edge) containment.next());
		graph.containment.clear();
		new LocalOptimizer().visit(graph);
	}

	double evaluateNodeOutgoing() {
		double result = super.evaluateNodeOutgoing();
		// result += Math.random() * rankSize * (1.0 - progress) / 3.0;
		if (progress > 0.2) {
			Subgraph s = node.getParent();
			double connectivity = mergeConnectivity(s, node.rank + 1, result,
					progress);
			result = connectivity;
		}
		return result;
	}

	double evaluateNodeIncoming() {
		double result = super.evaluateNodeIncoming();
		// result += Math.random() * rankSize * (1.0 - progress) / 3.0;
		if (progress > 0.2) {
			Subgraph s = node.getParent();
			double connectivity = mergeConnectivity(s, node.rank - 1, result,
					progress);
			result = connectivity;
		}
		return result;
	}

	double mergeConnectivity(Subgraph s, int row, double result,
			double scaleFactor) {
		while (s != null && getRowEntry(s, row) == null)
			s = s.getParent();
		if (s != null) {
			RowEntry entry = getRowEntry(s, row);
			double connectivity = entry.contribution / entry.count;
			result = connectivity * 0.3 + (0.7) * result;
			s = s.getParent();
		}
		return result;
	}

	RowEntry getRowEntry(Subgraph s, int row) {
		key.s = s;
		key.rank = row;
		return (RowEntry) map.get(key);
	}

	void copyConstraints(NestingTree tree) {
		if (tree.subgraph != null)
			tree.sortValue = tree.subgraph.rowOrder;
		for (int i = 0; i < tree.contents.size(); i++) {
			Object child = tree.contents.get(i);
			if (child instanceof Node) {
				Node n = (Node) child;
				n.sortValue = n.rowOrder;
			} else {
				copyConstraints((NestingTree) child);
			}
		}
	}

	public void init(DirectedGraph g) {
		super.init(g);
		init = true;

		for (int row = 0; row < g.ranks.size(); row++) {
			Rank rank = g.ranks.getRank(row);

			NestingTree tree = NestingTree.buildNestingTreeForRank(rank);
			copyConstraints(tree);
			tree.recursiveSort(true);
			rank.clear();
			tree.repopulateRank(rank);

			for (int j = 0; j < rank.count(); j++) {
				Node n = rank.getNode(j);
				Subgraph s = n.getParent();
				while (s != null) {
					addRowEntry(s, row);
					s = s.getParent();
				}
			}
		}
	}

	protected void postSort() {
		super.postSort();
		if (init)
			updateRank(rank);
	}

	void updateRank(Rank rank) {
		for (int j = 0; j < rank.count(); j++) {
			Node n = rank.getNode(j);
			Subgraph s = n.getParent();
			while (s != null) {
				getRowEntry(s, currentRow).reset();
				s = s.getParent();
			}
		}
		for (int j = 0; j < rank.count(); j++) {
			Node n = rank.getNode(j);
			Subgraph s = n.getParent();
			while (s != null) {
				RowEntry entry = getRowEntry(s, currentRow);
				entry.count++;
				entry.contribution += n.index;
				s = s.getParent();
			}
		}
	}

}
