/*******************************************************************************
 * Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
 * 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: The Chisel Group, University of Victoria
 *******************************************************************************/

package org.eclipse.zest.layouts.algorithms;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.zest.layouts.Filter;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutItem;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.Stoppable;
import org.eclipse.zest.layouts.constraints.BasicEntityConstraint;
import org.eclipse.zest.layouts.dataStructures.BendPoint;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentDimension;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentPoint;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle;
import org.eclipse.zest.layouts.dataStructures.InternalNode;
import org.eclipse.zest.layouts.dataStructures.InternalRelationship;
import org.eclipse.zest.layouts.progress.ProgressEvent;
import org.eclipse.zest.layouts.progress.ProgressListener;

/**
 * Handles common elements in all layout algorithms
 * [irbull] Refactored into a template pattern.  ApplyLayout now delegates the
 * task to ApplyLayoutInternal
 * 
 * [irbull] Included asynchronous layouts
 * 
 * @version 1.0
 * @author Casey Best
 * @author Ian Bull
 * @author Chris Callendar
 * @author Rob Lintern
 * @author Chris Bennett
 */
public abstract class AbstractLayoutAlgorithm implements LayoutAlgorithm, Stoppable {

	public void removeRelationships(Collection collection) {

	}

	public final static int MIN_ENTITY_SIZE = 5;
	private final static int MIN_TIME_DELAY_BETWEEN_PROGRESS_EVENTS = 1;

	private Thread creationThread = null;
	protected Comparator comparator;
	protected Filter filter;
	private List progressListeners;
	private Calendar lastProgressEventFired;
	private double widthToHeightRatio;

	class InternalComparator implements Comparator {
		Comparator externalComparator = null;

		public InternalComparator(Comparator externalComparator) {
			this.externalComparator = externalComparator;
		}

		public int compare(Object o1, Object o2) {
			InternalNode internalNode1 = (InternalNode) o1;
			InternalNode internalNode2 = (InternalNode) o2;

			return this.externalComparator.compare(internalNode1.getLayoutEntity(), internalNode2.getLayoutEntity());
		}

	}

	/*
	 * Internal Nodes.   
	 */
	private InternalNode[] internalNodes;
	private InternalRelationship[] internalRelationships;
	private double internalX;
	private double internalY;
	private double internalWidth;
	private double internalHeight;
	protected boolean internalContinuous;
	protected boolean internalAsynchronous;

	/*
	 * A queue of entities and relationships to add or remove.  Each layout 
	 * algorithm should check these and update their internal lists.
	 */

	/** A list of LayoutEntity objects to be removed from the layout. */
	private List entitiesToRemove;
	/** A list of LayoutRelationship objects to be removed. */
	private List relationshipsToRemove;
	/** A list of LayoutEntity objects to be added to the layout. */
	private List entitiesToAdd;
	/** A list of LayoutRelationship objects to be added. */
	private List relationshipsToAdd;

	//protected boolean cancelled = false;

	protected boolean layoutStopped = true;

	protected int layout_styles = 0;

	// Child classes can set to false to retain node shapes and sizes
	protected boolean resizeEntitiesAfterLayout = true;

	/**
	 * Initializes the abstract layout algorithm.
	 * @see LayoutStyles
	 */
	public AbstractLayoutAlgorithm(int styles) {
		this.creationThread = Thread.currentThread();
		this.progressListeners = new ArrayList();
		this.lastProgressEventFired = Calendar.getInstance();
		this.widthToHeightRatio = 1.0;

		this.entitiesToRemove = new ArrayList();
		this.relationshipsToRemove = new ArrayList();
		this.entitiesToAdd = new ArrayList();
		this.relationshipsToAdd = new ArrayList();
		this.layout_styles = styles;
	}

	/**
	 * Queues up the given entity (if it isn't in the list) to be added to the algorithm.
	 * @param entity
	 */
	public void addEntity(LayoutEntity entity) {
		if ((entity != null) && !entitiesToAdd.contains(entity)) {
			entitiesToAdd.add(entity);
		}
	}

	/**
	 * Queues up the given relationshp (if it isn't in the list) to be added to the algorithm.
	 * @param relationship
	 */
	public void addRelationship(LayoutRelationship relationship) {
		if ((relationship != null) && !relationshipsToAdd.contains(relationship)) {
			relationshipsToAdd.add(relationship);
		}
	}

	/**
	 * Queues up the given entity to be removed from the algorithm next time it runs.
	 * @param entity The entity to remove
	 */
	public void removeEntity(LayoutEntity entity) {
		if ((entity != null) && !entitiesToRemove.contains(entity)) {
			entitiesToRemove.add(entity);
		}
	}

	/**
	 * Queues up the given relationship to be removed from the algorithm next time it runs.
	 * @param relationship	The relationship to remove.
	 */
	public void removeRelationship(LayoutRelationship relationship) {
		if ((relationship != null) && !relationshipsToRemove.contains(relationship)) {
			relationshipsToRemove.add(relationship);
		}
	}

	/**
	 * Queues up all the relationships in the list to be removed.
	 * @param relationships
	 */
	public void removeRelationships(List relationships) {
		// note we don't check if the relationshipsToRemove contains
		// any of the objects in relationships.
		relationshipsToRemove.addAll(relationships);
	}

	/**
	 * Sets the current layout style.  This overwrites all other layout styles.
	 * Use getStyle to get the current style.
	 * @param style
	 */
	public void setStyle(int style) {
		this.layout_styles = style;
	}

	/**
	 * Gets the current layout style
	 * @return
	 */
	public int getStyle() {
		return this.layout_styles;
	}

	public abstract void setLayoutArea(double x, double y, double width, double height);

	/**
	 * Determines if the configuration is valid for this layout
	 * @param asynchronous
	 * @param continuous
	 */
	protected abstract boolean isValidConfiguration(boolean asynchronous, boolean continuous);

	/**
	 * Apply the layout to the given entities.  The entities will be moved and resized based
	 * on the algorithm.
	 * 
	 * @param entitiesToLayout Apply the algorithm to these entities
	 * @param relationshipsToConsider Only consider these relationships when applying the algorithm.
	 * @param x The left side of the bounds in which the layout can place the entities.
	 * @param y The top side of the bounds in which the layout can place the entities.
	 * @param width The width of the bounds in which the layout can place the entities.
	 * @param height The height of the bounds in which the layout can place the entities.
	 */
	abstract protected void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight);

	/**
	 * Updates the given array of entities checking if any need to be removed or added.
	 * @param entities the current entities
	 * @return the updated entities array
	 */
	protected InternalNode[] updateEntities(InternalNode[] entities) {
		if ((entitiesToRemove.size() > 0) || (entitiesToAdd.size() > 0)) {
			List internalNodesList = new ArrayList(Arrays.asList(entities));

			// remove nodes
			for (Iterator iter = entitiesToRemove.iterator(); iter.hasNext();) {
				LayoutEntity entity = (LayoutEntity) iter.next();
				if (entity.getLayoutInformation() != null) {
					internalNodesList.remove(entity.getLayoutInformation());
				}
			}

			// Also remove from _internalNodes
			ArrayList updatedEntities = new ArrayList(internalNodes.length - entitiesToRemove.size() + entitiesToAdd.size());
			for (int i = 0; i < internalNodes.length; i++) {
				InternalNode node = internalNodes[i];
				if (entitiesToRemove.contains(node.getLayoutEntity())) {
					entitiesToRemove.remove(node.getLayoutEntity());
				} else {
					updatedEntities.add(node);
				}
			}
			entitiesToRemove.clear();

			// Add any new nodes
			LayoutEntity[] entitiesArray = new LayoutEntity[entitiesToAdd.size()];
			entitiesArray = (LayoutEntity[]) entitiesToAdd.toArray(entitiesArray);
			InternalNode[] newNodes = createInternalNodes(entitiesArray);
			for (int i = 0; i < newNodes.length; i++) {
				internalNodesList.add(newNodes[i]);
				updatedEntities.add(newNodes[i]);
			}
			entitiesToAdd.clear();

			entities = new InternalNode[internalNodesList.size()];
			entities = (InternalNode[]) internalNodesList.toArray(entities);

			internalNodes = new InternalNode[updatedEntities.size()];
			internalNodes = (InternalNode[]) updatedEntities.toArray(internalNodes);
		}

		return entities;
	}

	/**
	 * Updates the given array of relationships checking if any need to be removed or added.
	 * Also updates the original array of relationships.
	 * @param relationships the current relationships
	 * @return the update relationships array
	 */
	protected InternalRelationship[] updateRelationships(InternalRelationship[] relationships) {
		if ((relationshipsToRemove.size() > 0) || (relationshipsToAdd.size() > 0)) {
			List internalRelsList = new ArrayList(Arrays.asList(relationships));

			// remove relationships
			if (relationshipsToRemove.size() > 0) {
				for (Iterator iter = relationshipsToRemove.iterator(); iter.hasNext();) {
					LayoutRelationship relation = (LayoutRelationship) iter.next();
					if (relation.getLayoutInformation() != null) {
						internalRelsList.remove(relation.getLayoutInformation());
					}
				}
			}

			// Also remove from _internalRelationships
			ArrayList updatedRelationships = new ArrayList(internalRelationships.length - relationshipsToRemove.size() + relationshipsToAdd.size());
			for (int i = 0; i < internalRelationships.length; i++) {
				InternalRelationship relation = internalRelationships[i];
				if (relationshipsToRemove.contains(relation.getLayoutRelationship())) {
					relationshipsToRemove.remove(relation.getLayoutRelationship());
				} else {
					updatedRelationships.add(relation);
				}
			}
			relationshipsToRemove.clear();

			// add relationships
			if (relationshipsToAdd.size() > 0) {
				LayoutRelationship[] relsArray = new LayoutRelationship[relationshipsToAdd.size()];
				relsArray = (LayoutRelationship[]) relationshipsToAdd.toArray(relsArray);
				InternalRelationship[] newRelationships = createInternalRelationships(relsArray);
				for (int i = 0; i < newRelationships.length; i++) {
					internalRelsList.add(newRelationships[i]);
					updatedRelationships.add(newRelationships[i]);
				}
			}
			relationshipsToAdd.clear();

			relationships = new InternalRelationship[internalRelsList.size()];
			relationships = (InternalRelationship[]) internalRelsList.toArray(relationships);

			internalRelationships = new InternalRelationship[updatedRelationships.size()];
			internalRelationships = (InternalRelationship[]) updatedRelationships.toArray(internalRelationships);
		}

		return relationships;
	}

	/**
	 * Moves all the entities by the given amount.  
	 * @param dx	the amount to shift the entities in the x-direction 
	 * @param dy	the amount to shift the entities in the y-direction
	 */
	/*
	 public void moveAllEntities(double dx, double dy) {
	 if ((dx != 0) || (dy != 0)) {
	 synchronized (_internalNodes) {
	 for (int i = 0; i < _internalNodes.length; i++) {
	 InternalNode node = _internalNodes[i];
	 node.setInternalLocation(node.getInternalX()+dx, node.getInternalY()+dy);
	 node.setLocation(node.getX()+dx, node.getY()+dy);
	 }
	 }
	 }
	 }
	 */

	/**
	 * Returns true if the layout algorithm is running
	 * @return boolean if the layout algorithm is running
	 */
	public synchronized boolean isRunning() {
		return !layoutStopped;
	}

	/**
	 * Stops the current layout from running.
	 * All layout algorithms should constantly check isLayoutRunning
	 */
	public synchronized void stop() {
		layoutStopped = true;
		postLayoutAlgorithm(internalNodes, internalRelationships);
		fireProgressEnded(getTotalNumberOfLayoutSteps());
	}

	//	/**
	//	 * Sleeps while the algorithm is paused.
	//	 */
	//	protected void sleepWhilePaused() {
	//		// do nothing while the algorithm is paused
	//		boolean wasPaused = false;
	//		while (isPaused()) {
	//			try {
	//				Thread.sleep(200);
	//			} catch (InterruptedException e) {
	//			}
	//			wasPaused = true;
	//		}
	//		// update the node positions (they might have been moved while paused)
	//		if (wasPaused) {
	//			for (int i = 0; i < internalNodes.length; i++) {
	//				InternalNode node = internalNodes[i];
	//				node.setInternalLocation(node.getPreferredX(), node.getPreferredY());
	//			}
	//		}
	//	}

	private void setupLayout(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
		internalX = x;
		internalY = y;
		internalHeight = height;
		internalWidth = width;
		// Filter all the unwanted entities and relationships
		entitiesToLayout = (LayoutEntity[]) filterUnwantedObjects(entitiesToLayout);
		relationshipsToConsider = (LayoutRelationship[]) filterUnwantedObjects(relationshipsToConsider);

		// Check that the input is valid
		if (!verifyInput(entitiesToLayout, relationshipsToConsider)) {
			layoutStopped = true;
			throw new RuntimeException("The relationships in relationshipsToConsider don't contain the entities in entitiesToLayout");
		}

		// Create the internal nodes and relationship
		internalNodes = createInternalNodes(entitiesToLayout);
		internalRelationships = createInternalRelationships(relationshipsToConsider);
	}

	//	public synchronized Stoppable getLayoutThread(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider, double x, double y, double width, double height, boolean continuous) {
	//		//setupLayout( entitiesToLayout, relationshipsToConsider, x, y, width, height );
	//		this.layoutStopped = false;
	//		this.runContinuously = continuous;
	//		setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
	//		preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
	//		fireProgressStarted(getTotalNumberOfLayoutSteps());
	//		return this;
	//	}

	/**
	 * Code called before the layout algorithm starts
	 */
	protected abstract void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height);

	/**
	 * Code called after the layout algorithm ends
	 */
	protected abstract void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider);

	/**
	 *  Gets the total number of steps in this layout
	 */
	protected abstract int getTotalNumberOfLayoutSteps();

	/**
	 * Gets the current layout step
	 * @return
	 */
	protected abstract int getCurrentLayoutStep();

	/**
	 * This actually applies the layout
	 */
	public synchronized void applyLayout(final LayoutEntity[] entitiesToLayout, final LayoutRelationship[] relationshipsToConsider, final double x, final double y, final double width, final double height, boolean asynchronous, boolean continuous) throws InvalidLayoutConfiguration {
		checkThread();
		this.internalAsynchronous = asynchronous;
		this.internalContinuous = continuous;

		if (!isValidConfiguration(asynchronous, continuous)) {
			throw new InvalidLayoutConfiguration();
		}

		clearBendPoints(relationshipsToConsider);

		this.layoutStopped = false;

		// when an algorithm starts, reset the progress event
		lastProgressEventFired = Calendar.getInstance();
		if (asynchronous) {

			Thread thread = new Thread(new Runnable() {

				public void run() {
					setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
					preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
					fireProgressStarted(getTotalNumberOfLayoutSteps());

					applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
					stop();
				}

			});
			thread.setPriority(Thread.MIN_PRIORITY);
			thread.start();
		} else {

			// If we are running synchronously then we have to stop this at some
			// point? right?
			setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
			preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
			fireProgressStarted(getTotalNumberOfLayoutSteps());

			applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
			stop();
		}

	}

	/**
	 * Clear out all old bend points before doing a layout
	 */
	private void clearBendPoints(LayoutRelationship[] relationships) {
		for (int i = 0; i < relationships.length; i++) {
			LayoutRelationship rel = relationships[i];
			rel.clearBendPoints();
		}
	}

	/**
	 * Update external bend points from the internal bendpoints list. Save the 
	 * source and destination points for later use in scaling and translating
	 * @param relationshipsToConsider
	 */
	protected void updateBendPoints(InternalRelationship[] relationshipsToConsider) {
		for (int i = 0; i < relationshipsToConsider.length; i++) {
			InternalRelationship relationship = relationshipsToConsider[i];
			List bendPoints = relationship.getBendPoints();
			if (bendPoints.size() > 0) {
				// We will assume that source/dest coordinates are for center of node
				BendPoint[] externalBendPoints = new BendPoint[bendPoints.size() + 2];
				InternalNode sourceNode = relationship.getSource();
				externalBendPoints[0] = new BendPoint(sourceNode.getInternalX(), sourceNode.getInternalY());
				InternalNode destNode = relationship.getDestination();
				externalBendPoints[externalBendPoints.length - 1] = new BendPoint(destNode.getInternalX(), destNode.getInternalY());

				for (int j = 0; j < bendPoints.size(); j++) {
					BendPoint bp = (BendPoint) bendPoints.get(j);
					externalBendPoints[j + 1] = new BendPoint(bp.x, bp.y, bp.getIsControlPoint());
				}
				relationship.getLayoutRelationship().setBendPoints(externalBendPoints);
			}
		}
	}

	//	public void run() {
	//
	//		if (started == true) {
	//			throw new RuntimeException("Layout has already run!");
	//		}
	//		started = true;
	//		//layoutStopped = false;
	//		isLayoutPaused = false;
	//		applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
	//		stop();
	//		layoutStopped = true;
	//		isLayoutPaused = false;
	//	}

	/**
	 * Creates a list of InternalNode objects from the list of LayoutEntity objects the user
	 * wants layed out. Sets the internal nodes' positions and sizes from the
	 * external entities.
	 */
	private InternalNode[] createInternalNodes(LayoutEntity[] nodes) {
		InternalNode[] internalNodes = new InternalNode[nodes.length];
		BasicEntityConstraint basicEntityConstraint = new BasicEntityConstraint();
		for (int i = 0; i < nodes.length; i++) {
			basicEntityConstraint.clear();
			LayoutEntity externalNode = nodes[i];
			InternalNode internalNode = new InternalNode(externalNode);
			externalNode.populateLayoutConstraint(basicEntityConstraint);
			internalNode.setInternalLocation(externalNode.getXInLayout(), externalNode.getYInLayout());
			internalNodes[i] = internalNode;
		} // end of for
		return internalNodes;
	}

	/**
	 * Creates a list of InternalRelationship objects from the given list of LayoutRelationship objects.
	 * @param rels
	 * @return List of internal relationships
	 */
	private InternalRelationship[] createInternalRelationships(LayoutRelationship[] rels) {
		ArrayList listOfInternalRelationships = new ArrayList(rels.length);
		for (int i = 0; i < rels.length; i++) {
			LayoutRelationship relation = rels[i];
			InternalNode src = (InternalNode) relation.getSourceInLayout().getLayoutInformation();
			InternalNode dest = (InternalNode) relation.getDestinationInLayout().getLayoutInformation();
			if ((src != null) && (dest != null)) {
				InternalRelationship internalRelationship = new InternalRelationship(relation, src, dest);
				listOfInternalRelationships.add(internalRelationship);
			} else {
				throw new RuntimeException("Error creating internal relationship, one of the nodes is null: src=" + src + ", dest=" + dest);
			}
		}
		InternalRelationship[] internalRelationships = new InternalRelationship[listOfInternalRelationships.size()];
		listOfInternalRelationships.toArray(internalRelationships);
		return internalRelationships;
	}

	/**
	 * Removes any objects that are currently filtered
	 */
	private Object[] filterUnwantedObjects(LayoutItem[] objects) {
		// first remove any entities or relationships that are filtered.
		List unfilteredObjsList = new ArrayList();
		if (filter != null) {
			for (int i = 0; i < objects.length; i++) {
				LayoutItem object = objects[i];
				if (!filter.isObjectFiltered(object)) {
					unfilteredObjsList.add(object);
				}
			}
			//@tag bug.156266-ClassCast.fix : use reflection to create the array.
			Object[] unfilteredObjs = (Object[]) java.lang.reflect.Array.newInstance(objects.getClass().getComponentType(), unfilteredObjsList.size());
			unfilteredObjsList.toArray(unfilteredObjs);
			return unfilteredObjs;
		}
		return objects;
	}

	/**
	 * Filters the entities and relationships to apply the layout on
	 */
	public void setFilter(Filter filter) {
		this.filter = filter;
	}

	/**
	 * Determines the order in which the objects should be displayed.
	 * Note: Some algorithms force a specific order.
	 */
	public void setComparator(Comparator comparator) {
		this.comparator = new InternalComparator(comparator);
	}

	/**
	 * Verifies the endpoints of the relationships are entities in the entitiesToLayout list.
	 * Allows other classes in this package to use this method to verify the input
	 */
	public static boolean verifyInput(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider) {
		boolean stillValid = true;
		for (int i = 0; i < relationshipsToConsider.length; i++) {
			LayoutRelationship relationship = relationshipsToConsider[i];
			LayoutEntity source = relationship.getSourceInLayout();
			LayoutEntity destination = relationship.getDestinationInLayout();
			boolean containsSrc = false;
			boolean containsDest = false;
			int j = 0;
			while (j < entitiesToLayout.length && !(containsSrc && containsDest)) {
				if (entitiesToLayout[j].equals(source)) {
					containsSrc = true;
				}
				if (entitiesToLayout[j].equals(destination)) {
					containsDest = true;
				}
				j++;
			}
			stillValid = containsSrc && containsDest;
		}
		return stillValid;
	}

	/**
	 * Gets the location in the layout bounds for this node
	 * @param x
	 * @param y
	 * @return
	 */
	protected DisplayIndependentPoint getLocalLocation(InternalNode[] entitiesToLayout, double x, double y, DisplayIndependentRectangle realBounds) {

		double screenWidth = realBounds.width;
		double screenHeight = realBounds.height;
		DisplayIndependentRectangle layoutBounds = getLayoutBounds(entitiesToLayout, false);
		double localX = (x / screenWidth) * layoutBounds.width + layoutBounds.x;
		double localY = (y / screenHeight) * layoutBounds.height + layoutBounds.y;
		return new DisplayIndependentPoint(localX, localY);
	}

	/**
	 * Find an appropriate size for the given nodes, then fit them into the given bounds.
	 * The relative locations of the nodes to each other must be preserved.
	 * Child classes should set flag reresizeEntitiesAfterLayout to false if they 
	 * want to preserve node sizes.
	 */
	protected void defaultFitWithinBounds(InternalNode[] entitiesToLayout, DisplayIndependentRectangle realBounds) {
		defaultFitWithinBounds(entitiesToLayout, new InternalRelationship[0], realBounds);
	}

	/**
	 * Find an appropriate size for the given nodes, then fit them into the given bounds.
	 * The relative locations of the nodes to each other must be preserved.
	 * Child classes should set flag reresizeEntitiesAfterLayout to false if they 
	 * want to preserve node sizes.
	 */
	protected void defaultFitWithinBounds(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle realBounds) {

		DisplayIndependentRectangle layoutBounds;

		if (resizeEntitiesAfterLayout) {
			layoutBounds = getLayoutBounds(entitiesToLayout, false);

			// Convert node x,y to be in percent rather than absolute coords
			convertPositionsToPercentage(entitiesToLayout, relationships, layoutBounds, false /*do not update size*/);

			// Resize and shift nodes
			resizeAndShiftNodes(entitiesToLayout);
		}

		// Recalculate layout, allowing for the node width, which we now know
		layoutBounds = getLayoutBounds(entitiesToLayout, true);

		// adjust node positions again, to the new coordinate system (still as a percentage)
		convertPositionsToPercentage(entitiesToLayout, relationships, layoutBounds, true /*update node size*/);

		DisplayIndependentRectangle screenBounds = calcScreenBounds(realBounds, layoutBounds);

		// Now convert to real screen coordinates
		convertPositionsToCoords(entitiesToLayout, relationships, screenBounds);
	}

	/**
	 * Calculate the screen bounds, maintaining the  
	 * @param realBounds
	 * @return
	 */
	private DisplayIndependentRectangle calcScreenBounds(DisplayIndependentRectangle realBounds, DisplayIndependentRectangle layoutBounds) {
		if (resizeEntitiesAfterLayout) { // OK to alter aspect ratio 
			double borderWidth = Math.min(realBounds.width, realBounds.height) / 10.0; // use 10% for the border - 5% on each side
			return new DisplayIndependentRectangle(realBounds.x + borderWidth / 2.0, realBounds.y + borderWidth / 2.0, realBounds.width - borderWidth, realBounds.height - borderWidth);
		} else { // retain layout aspect ratio 
			double heightAdjustment = realBounds.height / layoutBounds.height;
			double widthAdjustment = realBounds.width / layoutBounds.width;
			double ratio = Math.min(heightAdjustment, widthAdjustment);
			double adjustedHeight = layoutBounds.height * ratio;
			double adjustedWidth = layoutBounds.width * ratio;
			double adjustedX = realBounds.x + (realBounds.width - adjustedWidth) / 2.0;
			double adjustedY = realBounds.y + (realBounds.height - adjustedHeight) / 2.0;
			double borderWidth = Math.min(adjustedWidth, adjustedHeight) / 10.0; // use 10% for the border - 5% on each side
			return new DisplayIndependentRectangle(adjustedX + borderWidth / 2.0, adjustedY + borderWidth / 2.0, adjustedWidth - borderWidth, adjustedHeight - borderWidth);
		}
	}

	/**
	 * Find and set the node size - shift the nodes to the right and down to make 
	 * room for the width and height. 
	 * @param entitiesToLayout
	 * @param relationships
	 */
	private void resizeAndShiftNodes(InternalNode[] entitiesToLayout) {
		// get maximum node size as percent of screen dimmensions
		double nodeSize = getNodeSize(entitiesToLayout);
		double halfNodeSize = nodeSize / 2;

		// Resize and shift nodes
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode node = entitiesToLayout[i];
			node.setInternalSize(nodeSize, nodeSize);
			node.setInternalLocation(node.getInternalX() + halfNodeSize, node.getInternalY() + halfNodeSize);
		}
	}

	/**
	 * Convert all node positions into a percentage of the screen. If includeNodeSize
	 * is true then this also updates the node's internal size. 
	 * @param entitiesToLayout
	 */
	private void convertPositionsToPercentage(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle layoutBounds, boolean includeNodeSize) {

		// Adjust node positions and sizes
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode node = entitiesToLayout[i];
			DisplayIndependentPoint location = node.getInternalLocation().convertToPercent(layoutBounds);
			node.setInternalLocation(location.x, location.y);
			if (includeNodeSize) { // adjust node sizes
				double width = node.getInternalWidth() / layoutBounds.width;
				double height = node.getInternalHeight() / layoutBounds.height;
				node.setInternalSize(width, height);
			}
		}

		// Adjust bendpoint positions
		for (int i = 0; i < relationships.length; i++) {
			InternalRelationship rel = relationships[i];
			for (int j = 0; j < rel.getBendPoints().size(); j++) {
				BendPoint bp = (BendPoint) rel.getBendPoints().get(j);
				DisplayIndependentPoint toPercent = bp.convertToPercent(layoutBounds);
				bp.setX(toPercent.x);
				bp.setY(toPercent.y);
			}
		}
	}

	/**
	 * Convert the positions from a percentage of bounds area to fixed
	 * coordinates. NOTE: ALL OF THE POSITIONS OF NODES UNTIL NOW WERE FOR THE
	 * CENTER OF THE NODE - Convert it to the left top corner.
	 * 
	 * @param entitiesToLayout
	 * @param relationships
	 * @param realBounds
	 */
	private void convertPositionsToCoords(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle screenBounds) {

		// Adjust node positions and sizes
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode node = entitiesToLayout[i];
			double width = node.getInternalWidth() * screenBounds.width;
			double height = node.getInternalHeight() * screenBounds.height;
			DisplayIndependentPoint location = node.getInternalLocation().convertFromPercent(screenBounds);
			node.setInternalLocation(location.x - width / 2, location.y - height / 2);
			if (resizeEntitiesAfterLayout) {
				adjustNodeSizeAndPos(node, height, width);
			} else {
				node.setInternalSize(width, height);
			}
		}

		// Adjust bendpoint positions and shift based on source node size
		for (int i = 0; i < relationships.length; i++) {
			InternalRelationship rel = relationships[i];
			for (int j = 0; j < rel.getBendPoints().size(); j++) {
				BendPoint bp = (BendPoint) rel.getBendPoints().get(j);
				DisplayIndependentPoint fromPercent = bp.convertFromPercent(screenBounds);
				bp.setX(fromPercent.x);
				bp.setY(fromPercent.y);
			}
		}
	}

	/**
	 * Adjust node size to take advantage of space. Reset position to top left corner of node. 
	 * @param node
	 * @param height
	 * @param width
	 */
	private void adjustNodeSizeAndPos(InternalNode node, double height, double width) {
		double widthUsingHeight = height * widthToHeightRatio;
		if (widthToHeightRatio <= 1.0 && widthUsingHeight <= width) {
			double widthToUse = height * widthToHeightRatio;
			double leftOut = width - widthToUse;
			node.setInternalSize(Math.max(height * widthToHeightRatio, MIN_ENTITY_SIZE), Math.max(height, MIN_ENTITY_SIZE));
			node.setInternalLocation(node.getInternalX() + leftOut / 2, node.getInternalY());

		} else {
			double heightToUse = height / widthToHeightRatio;
			double leftOut = height - heightToUse;

			node.setInternalSize(Math.max(width, MIN_ENTITY_SIZE), Math.max(width / widthToHeightRatio, MIN_ENTITY_SIZE));
			node.setInternalLocation(node.getInternalX(), node.getInternalY() + leftOut / 2);
		}

	}

	/**
	 * Returns the maximum possible node size as a percentage of the width or height in current coord system.
	 */
	private double getNodeSize(InternalNode[] entitiesToLayout) {
		double width, height;
		if (entitiesToLayout.length == 1) {
			width = 0.8;
			height = 0.8;
		} else {
			DisplayIndependentDimension minimumDistance = getMinimumDistance(entitiesToLayout);
			width = 0.8 * minimumDistance.width;
			height = 0.8 * minimumDistance.height;
		}
		return Math.max(width, height);
	}

	/**
	 * Find the bounds in which the nodes are located.  Using the bounds against the real bounds
	 * of the screen, the nodes can proportionally be placed within the real bounds.
	 * The bounds can be determined either including the size of the nodes or not.  If the size
	 * is not included, the bounds will only be guaranteed to include the center of each node.
	 */
	protected DisplayIndependentRectangle getLayoutBounds(InternalNode[] entitiesToLayout, boolean includeNodeSize) {
		double rightSide = Double.MIN_VALUE;
		double bottomSide = Double.MIN_VALUE;
		double leftSide = Double.MAX_VALUE;
		double topSide = Double.MAX_VALUE;
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode entity = entitiesToLayout[i];
			if (entity.hasPreferredLocation()) {
				continue;
			}

			if (includeNodeSize) {
				leftSide = Math.min(entity.getInternalX() - entity.getInternalWidth() / 2, leftSide);
				topSide = Math.min(entity.getInternalY() - entity.getInternalHeight() / 2, topSide);
				rightSide = Math.max(entity.getInternalX() + entity.getInternalWidth() / 2, rightSide);
				bottomSide = Math.max(entity.getInternalY() + entity.getInternalHeight() / 2, bottomSide);
			} else {
				leftSide = Math.min(entity.getInternalX(), leftSide);
				topSide = Math.min(entity.getInternalY(), topSide);
				rightSide = Math.max(entity.getInternalX(), rightSide);
				bottomSide = Math.max(entity.getInternalY(), bottomSide);
			}
		}
		return new DisplayIndependentRectangle(leftSide, topSide, rightSide - leftSide, bottomSide - topSide);
	}

	/** 
	 * minDistance is the closest that any two points are together.
	 * These two points become the center points for the two closest nodes, 
	 * which we wish to make them as big as possible without overlapping.
	 * This will be the maximum of minDistanceX and minDistanceY minus a bit, lets say 20%
	 * 
	 * We make the recommended node size a square for convenience.
	 * 
	 * 
	 *    _______
	 *   |       | 
	 *   |       |
	 *   |   +   |
	 *   |   |\  |
	 *   |___|_\_|_____
	 *       | | \     |
	 *       | |  \    |
	 *       +-|---+   |
	 *         |       |
	 *         |_______|
	 * 
	 *  
	 * 
	 */
	private DisplayIndependentDimension getMinimumDistance(InternalNode[] entitiesToLayout) {
		DisplayIndependentDimension horAndVertdistance = new DisplayIndependentDimension(Double.MAX_VALUE, Double.MAX_VALUE);
		double minDistance = Double.MAX_VALUE; // the minimum distance between all the nodes
		//TODO: Very Slow!
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode layoutEntity1 = entitiesToLayout[i];
			double x1 = layoutEntity1.getInternalX();
			double y1 = layoutEntity1.getInternalY();
			for (int j = i + 1; j < entitiesToLayout.length; j++) {
				InternalNode layoutEntity2 = entitiesToLayout[j];
				double x2 = layoutEntity2.getInternalX();
				double y2 = layoutEntity2.getInternalY();
				double distanceX = Math.abs(x1 - x2);
				double distanceY = Math.abs(y1 - y2);
				double distance = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));

				if (distance < minDistance) {
					minDistance = distance;
					horAndVertdistance.width = distanceX;
					horAndVertdistance.height = distanceY;
				}
			}
		}
		return horAndVertdistance;
	}

	/**
	 * Set the width to height ratio you want the entities to use
	 */
	public void setEntityAspectRatio(double ratio) {
		widthToHeightRatio = ratio;
	}

	/**
	 * Returns the width to height ratio this layout will use to set the size of the entities.
	 */
	public double getEntityAspectRatio() {
		return widthToHeightRatio;
	}

	/**
	 * A layout algorithm could take an uncomfortable amout of time to complete.  To relieve some of
	 * the mystery, the layout algorithm will notify each ProgressListener of its progress. 
	 */
	public void addProgressListener(ProgressListener listener) {
		if (!progressListeners.contains(listener)) {
			progressListeners.add(listener);
		}
	}

	/**
	 * Removes the given progress listener, preventing it from receiving any more updates.
	 */
	public void removeProgressListener(ProgressListener listener) {
		if (progressListeners.contains(listener)) {
			progressListeners.remove(listener);
		}
	}

	/**
	 * Updates the layout locations so the external nodes know about the new locations
	 */
	protected void updateLayoutLocations(InternalNode[] nodes) {
		for (int i = 0; i < nodes.length; i++) {
			InternalNode node = nodes[i];
			if (!node.hasPreferredLocation()) {
				node.setLocation(node.getInternalX(), node.getInternalY());

				if ((layout_styles & LayoutStyles.NO_LAYOUT_NODE_RESIZING) != 1) {
					// Only set the size if we are supposed to
					node.setSize(node.getInternalWidth(), node.getInternalHeight());
				}
			}
		}
	}

	protected void fireProgressStarted(int totalNumberOfSteps) {
		ProgressEvent event = new ProgressEvent(0, totalNumberOfSteps);
		for (int i = 0; i < progressListeners.size(); i++) {
			ProgressListener listener = (ProgressListener) progressListeners.get(i);

			listener.progressStarted(event);
		}
	}

	protected void fireProgressEnded(int totalNumberOfSteps) {
		ProgressEvent event = new ProgressEvent(totalNumberOfSteps, totalNumberOfSteps);
		for (int i = 0; i < progressListeners.size(); i++) {
			ProgressListener listener = (ProgressListener) progressListeners.get(i);
			listener.progressEnded(event);
		}

	}

	/**
	 * Fires an event to notify all of the registered ProgressListeners that another step
	 * has been completed in the algorithm.
	 * @param currentStep The current step completed.
	 * @param totalNumberOfSteps The total number of steps in the algorithm.
	 */
	protected void fireProgressEvent(int currentStep, int totalNumberOfSteps) {

		// Update the layout locations to the external nodes
		Calendar now = Calendar.getInstance();
		now.add(Calendar.MILLISECOND, -MIN_TIME_DELAY_BETWEEN_PROGRESS_EVENTS);

		if (now.after(lastProgressEventFired) || currentStep == totalNumberOfSteps) {
			ProgressEvent event = new ProgressEvent(currentStep, totalNumberOfSteps);

			for (int i = 0; i < progressListeners.size(); i++) {

				ProgressListener listener = (ProgressListener) progressListeners.get(i);
				listener.progressUpdated(event);
			}
			lastProgressEventFired = Calendar.getInstance();
		}

	}

	protected int getNumberOfProgressListeners() {
		return progressListeners.size();
	}

	private void checkThread() {
		if (this.creationThread != Thread.currentThread()) {
			throw new RuntimeException("Invalid Thread Access.");
		}
	}

}
