/*******************************************************************************
 * Copyright (c) 2005, 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.Collection;

/**
 * A group of nodes which are interlocked and cannot be separately placed.
 * 
 * @since 3.1
 */
class NodeCluster extends NodeList {

	int hashCode = new Object().hashCode();

	boolean isSetMember;
	boolean isDirty;
	boolean leftDirty;
	boolean rightDirty;

	int leftFreedom;
	int rightFreedom;
	int leftNonzero;
	int rightNonzero;
	int leftCount = 0;
	int rightCount = 0;

	CollapsedEdges leftLinks[] = new CollapsedEdges[10];
	CollapsedEdges rightLinks[] = new CollapsedEdges[10];
	NodeCluster leftNeighbors[] = new NodeCluster[10];
	NodeCluster rightNeighbors[] = new NodeCluster[10];

	int effectivePull;
	int weightedTotal;
	int weightedDivisor;
	int unweightedTotal;
	int unweightedDivisor;

	void addLeftNeighbor(NodeCluster neighbor, CollapsedEdges link) {
		// Need to grow array in the following case
		if (leftNeighbors.length == leftCount) {
			int newSize = leftNeighbors.length * 2;

			NodeCluster newNeighbors[] = new NodeCluster[newSize];
			CollapsedEdges newLinks[] = new CollapsedEdges[newSize];

			System.arraycopy(leftNeighbors, 0, newNeighbors, 0,
					leftNeighbors.length);
			System.arraycopy(leftLinks, 0, newLinks, 0, leftLinks.length);

			leftNeighbors = newNeighbors;
			leftLinks = newLinks;
		}
		leftNeighbors[leftCount] = neighbor;
		leftLinks[leftCount++] = link;
	}

	void addRightNeighbor(NodeCluster neighbor, CollapsedEdges link) {
		if (rightNeighbors.length == rightCount) {
			int newSize = rightNeighbors.length * 2;

			NodeCluster newNeighbors[] = new NodeCluster[newSize];
			CollapsedEdges newLinks[] = new CollapsedEdges[newSize];

			System.arraycopy(rightNeighbors, 0, newNeighbors, 0,
					rightNeighbors.length);
			System.arraycopy(rightLinks, 0, newLinks, 0, rightLinks.length);

			rightNeighbors = newNeighbors;
			rightLinks = newLinks;
		}
		rightNeighbors[rightCount] = neighbor;
		rightLinks[rightCount++] = link;
	}

	public void adjustRank(int delta, Collection affected) {
		adjustRank(delta);
		NodeCluster neighbor;
		CollapsedEdges edges;
		for (int i = 0; i < leftCount; i++) {
			neighbor = leftNeighbors[i];
			if (neighbor.isSetMember)
				continue;
			edges = leftLinks[i];

			neighbor.weightedTotal += delta * edges.collapsedWeight;
			neighbor.unweightedTotal += delta * edges.collapsedCount;

			weightedTotal -= delta * edges.collapsedWeight;
			unweightedTotal -= delta * edges.collapsedCount;

			neighbor.rightDirty = leftDirty = true;
			if (!neighbor.isDirty) {
				neighbor.isDirty = true;
				affected.add(neighbor);
			}
		}
		for (int i = 0; i < rightCount; i++) {
			neighbor = rightNeighbors[i];
			if (neighbor.isSetMember)
				continue;
			edges = rightLinks[i];

			neighbor.weightedTotal += delta * edges.collapsedWeight;
			neighbor.unweightedTotal += delta * edges.collapsedCount;

			weightedTotal -= delta * edges.collapsedWeight;
			unweightedTotal -= delta * edges.collapsedCount;

			neighbor.leftDirty = rightDirty = true;
			if (!neighbor.isDirty) {
				neighbor.isDirty = true;
				affected.add(neighbor);
			}
		}
		isDirty = true;
		affected.add(this);
	}

	public boolean equals(Object o) {
		return o == this;
	}

	CollapsedEdges getLeftNeighbor(NodeCluster neighbor) {
		for (int i = 0; i < leftCount; i++) {
			if (leftNeighbors[i] == neighbor)
				return leftLinks[i];
		}
		return null;
	}

	int getPull() {
		return effectivePull;
	}

	CollapsedEdges getRightNeighbor(NodeCluster neighbor) {
		for (int i = 0; i < rightCount; i++) {
			if (rightNeighbors[i] == neighbor)
				return rightLinks[i];
		}
		return null;
	}

	public int hashCode() {
		return hashCode;
	}

	/**
	 * Initializes pull and freedom values.
	 */
	void initValues() {
		weightedTotal = 0;
		weightedDivisor = 0;
		unweightedTotal = 0;
		int slack;

		leftNonzero = rightNonzero = leftFreedom = rightFreedom = Integer.MAX_VALUE;
		for (int i = 0; i < leftCount; i++) {
			CollapsedEdges edges = leftLinks[i];
			weightedTotal -= edges.getWeightedPull();
			unweightedTotal -= edges.tightestEdge.getSlack();
			unweightedDivisor += edges.collapsedCount;
			weightedDivisor += edges.collapsedWeight;
			slack = edges.tightestEdge.getSlack();
			leftFreedom = Math.min(slack, leftFreedom);
			if (slack > 0)
				leftNonzero = Math.min(slack, leftNonzero);
		}
		for (int i = 0; i < rightCount; i++) {
			CollapsedEdges edges = rightLinks[i];
			weightedTotal += edges.getWeightedPull();
			unweightedDivisor += edges.collapsedCount;
			unweightedTotal += edges.tightestEdge.getSlack();
			weightedDivisor += edges.collapsedWeight;
			slack = edges.tightestEdge.getSlack();
			rightFreedom = Math.min(slack, rightFreedom);
			if (slack > 0)
				rightNonzero = Math.min(slack, rightNonzero);
		}
		updateEffectivePull();
	}

	/**
	 * Refreshes the left and right freedom.
	 */
	void refreshValues() {
		int slack;
		isDirty = false;
		if (leftDirty) {
			leftDirty = false;
			leftNonzero = leftFreedom = Integer.MAX_VALUE;
			for (int i = 0; i < leftCount; i++) {
				CollapsedEdges edges = leftLinks[i];
				slack = edges.tightestEdge.getSlack();
				leftFreedom = Math.min(slack, leftFreedom);
				if (slack > 0)
					leftNonzero = Math.min(slack, leftNonzero);
			}
		}
		if (rightDirty) {
			rightDirty = false;
			rightNonzero = rightFreedom = Integer.MAX_VALUE;
			for (int i = 0; i < rightCount; i++) {
				CollapsedEdges edges = rightLinks[i];
				slack = edges.tightestEdge.getSlack();
				rightFreedom = Math.min(slack, rightFreedom);
				if (slack > 0)
					rightNonzero = Math.min(slack, rightNonzero);
			}
		}
		updateEffectivePull();
	}

	private void updateEffectivePull() {
		if (weightedDivisor != 0)
			effectivePull = weightedTotal / weightedDivisor;
		else if (unweightedDivisor != 0)
			effectivePull = unweightedTotal / unweightedDivisor;
		else
			effectivePull = 0;
	}

}