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

import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle;
import org.eclipse.zest.layouts.dataStructures.InternalNode;
import org.eclipse.zest.layouts.dataStructures.InternalRelationship;

/**
 * The SpringLayoutAlgorithm has its own data repository and relation
 * repository. A user can populate the repository, specify the layout
 * conditions, do the computation and query the computed results.
 * <p>
 * Instructions for using SpringLayoutAlgorithm: <br>
 * 1. Instantiate a SpringLayout object; <br>
 * 2. Populate the data repository using {@link #add add(...)}; <br>
 * 3. Populate the relation repository using
 * {@link #addRelation addRelation(...)}; <br>
 * 4. Execute {@link #compute compute()}; <br>
 * 5. Execute {@link #fitWithinBounds fitWithinBounds(...)}; <br>
 * 6. Query the computed results(node size and node position).
 * 
 * @version 2.0
 * @author Ian Bull
 * @author Casey Best (version 1.0 by Jingwei Wu/Rob Lintern)
 */
public class SpringLayoutAlgorithm extends ContinuousLayoutAlgorithm {

	private final static boolean DEFAULT_ANCHOR = false;

	/**
	 * The default value for the spring layout number of interations.
	 */
	public static final int DEFAULT_SPRING_ITERATIONS = 1000;

	/**
	 * the default value for the time algorithm runs.
	 */
	public static final long MAX_SPRING_TIME = 10000;

	/**
	 * The default value for positioning nodes randomly.
	 */
	public static final boolean DEFAULT_SPRING_RANDOM = true;

	/**
	 * The default value for ignoring unconnected nodes.
	 */
	public static final boolean DEFAULT_SPRING_IGNORE_UNCON = true;

	/**
	 * The default value for separating connected components.
	 */
	public static final boolean DEFAULT_SPRING_SEPARATE_COMPONENTS = true;

	/**
	 * The default value for the spring layout move-control.
	 */
	public static final double DEFAULT_SPRING_MOVE = 1.0f;

	/**
	 * The default value for the spring layout strain-control.
	 */
	public static final double DEFAULT_SPRING_STRAIN = 1.0f;

	/**
	 * The default value for the spring layout length-control.
	 */
	public static final double DEFAULT_SPRING_LENGTH = 1.0f;

	/**
	 * The default value for the spring layout gravitation-control.
	 */
	public static final double DEFAULT_SPRING_GRAVITATION = 1.0f;

	/**
	 * The variable can be customized to set the number of iterations used.
	 */
	private static int sprIterations = DEFAULT_SPRING_ITERATIONS;

	/**
	 * This variable can be customized to set the max number of MS the algorithm
	 * should run
	 */
	private static long maxTimeMS = MAX_SPRING_TIME;

	/**
	 * The variable can be customized to set whether or not the spring layout
	 * nodes are positioned randomly before beginning iterations.
	 */
	private static boolean sprRandom = DEFAULT_SPRING_RANDOM;

	/**
	 * Minimum distance considered between nodes
	 */
	protected static final double MIN_DISTANCE = 0.001d;

	/**
	 * An arbitrarily small value in mathematics.
	 */
	protected static final double EPSILON = 0.001d;

	/**
	 * The variable can be customerized to set the spring layout move-control.
	 */
	private static double sprMove = DEFAULT_SPRING_MOVE;

	/**
	 * The variable can be customized to set the spring layout strain-control.
	 */
	private static double sprStrain = DEFAULT_SPRING_STRAIN;

	/**
	 * The variable can be customized to set the spring layout length-control.
	 */
	private static double sprLength = DEFAULT_SPRING_LENGTH;

	/**
	 * The variable can be customized to set the spring layout
	 * gravitation-control.
	 */
	private static double sprGravitation = DEFAULT_SPRING_GRAVITATION;

	/**
	 * The largest movement of all vertices that has occured in the most recent
	 * iteration.
	 */
	private double largestMovement = 0;

	/**
	 * Maps a src and dest object to the number of relations between them. Key
	 * is src.toString() + dest.toString(), value is an Integer
	 */
	private Map srcDestToNumRelsMap;

	/**
	 * Maps a src and dest object to the average weight of the relations between
	 * them. Key is src.toString() + dest.toString(), value is a Double
	 */
	private Map srcDestToRelsAvgWeightMap;

	/**
	 * Maps a relationship type to a weight. Key is a string, value is a Double
	 */
	private static Map relTypeToWeightMap = new HashMap();

	private int iteration;

	private int[][] srcDestToNumRels;

	private double[][] srcDestToRelsAvgWeight;

	private double[] tempLocationsX;

	private double[] tempLocationsY;

	private double[] forcesX;

	private double[] forcesY;

	private boolean[] anchors;

	private DisplayIndependentRectangle bounds = null;

	Date date = null;

	/**
	 * Constructor.
	 */
	public SpringLayoutAlgorithm(int styles) {
		super(styles);
		srcDestToNumRelsMap = new HashMap();
		srcDestToRelsAvgWeightMap = new HashMap();
		date = new Date();
	}

	/**
	 * Creates a sprint layout algoirthm with no style
	 *
	 */
	public SpringLayoutAlgorithm() {
		this(LayoutStyles.NONE);
	}

	public void setLayoutArea(double x, double y, double width, double height) {
		bounds = new DisplayIndependentRectangle(x, y, width, height);
	}

	/**
	 * Sets the spring layout move-control.
	 * 
	 * @param move
	 *            The move-control value.
	 */
	public void setSpringMove(double move) {
		sprMove = move;
	}

	/**
	 * Returns the move-control value of this SpringLayoutAlgorithm in
	 * double presion.
	 * 
	 * @return The move-control value.
	 */
	public double getSpringMove() {
		return sprMove;
	}

	/**
	 * Sets the spring layout strain-control.
	 * 
	 * @param strain
	 *            The strain-control value.
	 */
	public void setSpringStrain(double strain) {
		sprStrain = strain;
	}

	/**
	 * Returns the strain-control value of this SpringLayoutAlgorithm in
	 * double presion.
	 * 
	 * @return The strain-control value.
	 */
	public double getSpringStrain() {
		return sprStrain;
	}

	/**
	 * Sets the spring layout length-control.
	 * 
	 * @param length
	 *            The length-control value.
	 */
	public void setSpringLength(double length) {
		sprLength = length;
	}

	/**
	 * Gets the max time this algorithm will run for
	 * 
	 * @return
	 */
	public long getSpringTimeout() {
		return maxTimeMS;
	}

	/**
	 * Sets the spring timeout
	 * 
	 * @param timeout
	 */
	public void setSpringTimeout(long timeout) {
		maxTimeMS = timeout;
	}

	/**
	 * Returns the length-control value of this SpringLayoutAlgorithm in
	 * double presion.
	 * 
	 * @return The length-control value.
	 */
	public double getSpringLength() {
		return sprLength;
	}

	/**
	 * Sets the spring layout gravitation-control.
	 * 
	 * @param gravitation
	 *            The gravitation-control value.
	 */
	public void setSpringGravitation(double gravitation) {
		sprGravitation = gravitation;
	}

	/**
	 * Returns the gravitation-control value of this SpringLayoutAlgorithm
	 * in double presion.
	 * 
	 * @return The gravitation-control value.
	 */
	public double getSpringGravitation() {
		return sprGravitation;
	}

	/**
	 * Sets the number of iterations to be used.
	 * 
	 * @param gravitation
	 *            The number of iterations.
	 */
	public void setIterations(int iterations) {
		sprIterations = iterations;
	}

	/**
	 * Returns the number of iterations to be used.
	 * 
	 * @return The number of iterations.
	 */
	public int getIterations() {
		return sprIterations;
	}

	/**
	 * Sets whether or not this SpringLayoutAlgorithm will layout the
	 * nodes randomly before beginning iterations.
	 * 
	 * @param random
	 *            The random placement value.
	 */
	public void setRandom(boolean random) {
		sprRandom = random;
	}

	/**
	 * Returns whether or not this SpringLayoutAlgorithm will layout the
	 * nodes randomly before beginning iterations.
	 */
	public boolean getRandom() {
		return sprRandom;
	}

	public void setWeight(String relType, double weight) {
		relTypeToWeightMap.put(relType, new Double(weight));
	}

	public double getWeight(String relType) {
		Double weight = (Double) relTypeToWeightMap.get(relType);
		return (weight == null) ? 1 : weight.doubleValue();
	}

	/**
	 * Sets the default conditions.
	 */
	public void setDefaultConditions() {
		// sprMove = DEFAULT_SPRING_MOVE;
		// sprStrain = DEFAULT_SPRING_STRAIN;
		// sprLength = DEFAULT_SPRING_LENGTH;
		// sprGravitation = DEFAULT_SPRING_GRAVITATION;
		// sprIterations = DEFAULT_SPRING_ITERATIONS;
	}

	/**
	 * Clean up after done
	 * 
	 * @param entitiesToLayout
	 */
	private void reset(InternalNode[] entitiesToLayout) {
		tempLocationsX = null;
		tempLocationsY = null;
		forcesX = null;
		forcesY = null;
		anchors = null;
		setDefaultConditions();
		srcDestToNumRelsMap = new HashMap();
		srcDestToRelsAvgWeightMap = new HashMap();
		relTypeToWeightMap = new HashMap();
	}

	private long startTime = 0;

	protected void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
		// TODO: Filter out any non-wanted entities and relationships
		// super.applyLayout(entitiesToLayout, relationshipsToConsider, x, y,
		// width, height);
		//InternalNode[] a_entitiesToLayout = (InternalNode[]) entitiesToLayout.toArray(new InternalNode[entitiesToLayout.size()]);
		bounds = new DisplayIndependentRectangle(x, y, width, height);
		tempLocationsX = new double[entitiesToLayout.length];
		tempLocationsY = new double[entitiesToLayout.length];
		forcesX = new double[entitiesToLayout.length];
		forcesY = new double[entitiesToLayout.length];
		anchors = new boolean[entitiesToLayout.length];

		for (int i = 0; i < entitiesToLayout.length; i++) {
			anchors[i] = DEFAULT_ANCHOR;
		}
		for (int i = 0; i < relationshipsToConsider.length; i++) {
			InternalRelationship layoutRelationship = relationshipsToConsider[i];
			addRelation(layoutRelationship);
		}

		// do the calculations
		preCompute(entitiesToLayout);
		startTime = date.getTime();
	}

	protected void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider) {
		reset(entitiesToLayout);
	}

	/**
	 * Adds a simple relation between two nodes to the relation repository.
	 * 
	 * @param layoutRelationship
	 *            The simple relation to be added
	 * @throws java.lang.NullPointerExcetption
	 *             If <code>sr</code> is null
	 * @see SimpleRelation
	 */
	private void addRelation(InternalRelationship layoutRelationship) {
		if (layoutRelationship == null) {
			throw new IllegalArgumentException("The arguments can not be null!");
		} else {
			double weight = layoutRelationship.getWeight();
			weight = (weight <= 0 ? 0.1 : weight);
			String key1 = layoutRelationship.getSource().toString() + layoutRelationship.getDestination().toString();
			String key2 = layoutRelationship.getDestination().toString() + layoutRelationship.getSource().toString();
			String[] keys = { key1, key2 };
			for (int i = 0; i < keys.length; i++) {
				String key = keys[i];
				Integer count = (Integer) srcDestToNumRelsMap.get(key);
				Double avgWeight = (Double) srcDestToRelsAvgWeightMap.get(key);
				if (count == null) {
					count = new Integer(1);
					avgWeight = new Double(weight);
				} else {
					int newCount = count.intValue() + 1;
					double newAverage = (avgWeight.doubleValue() * count.doubleValue() + weight) / newCount;
					avgWeight = new Double(newAverage);
					count = new Integer(newCount);
				}
				srcDestToNumRelsMap.put(key, count);
				srcDestToRelsAvgWeightMap.put(key, avgWeight);
			}
		}
	}

	private void preCompute(InternalNode[] entitiesToLayout) {
		// count number of relationships between all nodes and the average
		// weight between them
		srcDestToNumRels = new int[entitiesToLayout.length][entitiesToLayout.length];
		srcDestToRelsAvgWeight = new double[entitiesToLayout.length][entitiesToLayout.length];

		for (int i = 0; i < entitiesToLayout.length - 1; i++) {
			InternalNode layoutEntity1 = entitiesToLayout[i];
			for (int j = i + 1; j < entitiesToLayout.length; j++) {
				InternalNode layoutEntity2 = entitiesToLayout[j];
				srcDestToNumRels[i][j] = numRelations(layoutEntity1, layoutEntity2);
				srcDestToNumRels[i][j] += numRelations(layoutEntity2, layoutEntity1);
				srcDestToRelsAvgWeight[i][j] = avgWeight(layoutEntity1, layoutEntity2);
			}
		}

		if (sprRandom)
			placeRandomly(entitiesToLayout); // put vertices in random places
		else
			convertToUnitCoordinates(entitiesToLayout);

		iteration = 1;
		largestMovement = Double.MAX_VALUE;
	}

	// TODO: This is a complete Clone! (and not in a good way)
	protected DisplayIndependentRectangle getLayoutBoundsTemp(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++) {
			double x = tempLocationsX[i];
			double y = tempLocationsY[i];

			leftSide = Math.min(x, leftSide);
			topSide = Math.min(y, topSide);
			rightSide = Math.max(x, rightSide);
			bottomSide = Math.max(y, bottomSide);

		}
		return new DisplayIndependentRectangle(leftSide, topSide, rightSide - leftSide, bottomSide - topSide);
	}

	protected void convertNodePositionsBack(int i, InternalNode entityToConvert, double px, double py, double screenWidth, double screenHeight, DisplayIndependentRectangle layoutBounds) {

		// If the node selected is outside the screen, map it to the boarder
		if (px > screenWidth)
			px = screenWidth;
		if (py > screenHeight)
			py = screenHeight;

		if (px < 0)
			px = 1;
		if (py < 0)
			py = 1;

		double x = (px / screenWidth) * layoutBounds.width + layoutBounds.x;
		double y = (py / screenHeight) * layoutBounds.height + layoutBounds.y;

		tempLocationsX[i] = x;
		tempLocationsY[i] = y;
		//setTempLocation(entityToConvert, new DisplayIndependentPoint(x, y));

		if (entityToConvert.getInternalX() < 0) {
			// System.out.println("We have nodes less than 0 here!");
		}

	}

	private void checkPreferredLocation(InternalNode[] entitiesToLayout, DisplayIndependentRectangle realBounds) {
		// use 10% for the border - 5% on each side
		double borderWidth = Math.min(realBounds.width, realBounds.height) / 10.0;
		DisplayIndependentRectangle screenBounds = new DisplayIndependentRectangle(realBounds.x + borderWidth / 2.0, realBounds.y + borderWidth / 2.0, realBounds.width - borderWidth, realBounds.height - borderWidth);

		DisplayIndependentRectangle layoutBounds = getLayoutBoundsTemp(entitiesToLayout, false);
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode layoutEntity = entitiesToLayout[i];
			if (layoutEntity.hasPreferredLocation()) {
				convertNodePositionsBack(i, layoutEntity, layoutEntity.getPreferredX(), layoutEntity.getPreferredY(), screenBounds.width, screenBounds.height, layoutBounds);
			}
		}
	}

	/**
	 * Scales the current iteration counter based on how long the algorithm has
	 * been running for. You can set the MaxTime in maxTimeMS!
	 */
	private void setSprIterationsBasedOnTime() {
		if (maxTimeMS <= 0)
			return;

		long currentTime = date.getTime();
		double fractionComplete = (double) ((double) (currentTime - startTime) / ((double) maxTimeMS));
		int currentIteration = (int) (fractionComplete * sprIterations);
		if (currentIteration > iteration) {
			iteration = currentIteration;
		}

	}

	protected boolean performAnotherNonContinuousIteration() {
		setSprIterationsBasedOnTime();
		if (iteration <= sprIterations && largestMovement >= sprMove)
			return true;
		else
			return false;
	}

	protected int getCurrentLayoutStep() {
		return iteration;
	}

	protected int getTotalNumberOfLayoutSteps() {
		return sprIterations;
	}

	protected void computeOneIteration(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
		if (bounds == null)
			bounds = new DisplayIndependentRectangle(x, y, width, height);
		checkPreferredLocation(entitiesToLayout, bounds);
		computeForces(entitiesToLayout);
		largestMovement = Double.MAX_VALUE;
		computePositions(entitiesToLayout);

		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode layoutEntity = entitiesToLayout[i];
			layoutEntity.setInternalLocation(tempLocationsX[i], tempLocationsY[i]);
		}

		defaultFitWithinBounds(entitiesToLayout, bounds);

		iteration++;
	}

	/**
	 * Puts vertices in random places, all between (0,0) and (1,1).
	 */
	public void placeRandomly(InternalNode[] entitiesToLayout) {
		// If only one node in the data repository, put it in the middle
		if (entitiesToLayout.length == 1) {
			// If only one node in the data repository, put it in the middle
			tempLocationsX[0] = 0.5;
			tempLocationsY[0] = 0.5;
		} else {
			for (int i = 0; i < entitiesToLayout.length; i++) {
				if (i == 0) {
					tempLocationsX[i] = 0.0;
					tempLocationsY[i] = 0.0;
				} else if (i == 1) {
					tempLocationsX[i] = 1.0;
					tempLocationsY[i] = 1.0;
				} else {
					tempLocationsX[i] = Math.random();
					tempLocationsY[i] = Math.random();
				}
			}
		}
	}

	// /////////////////////////////////////////////////////////////////
	// /// Protected Methods /////
	// /////////////////////////////////////////////////////////////////

	/**
	 * Computes the force for each node in this SpringLayoutAlgorithm. The
	 * computed force will be stored in the data repository
	 */
	protected void computeForces(InternalNode[] entitiesToLayout) {

		// initialize all forces to zero
		for (int i = 0; i < entitiesToLayout.length; i++) {
			forcesX[i] = 0.0;
			forcesY[i] = 0.0;
		}

		// TODO: Again really really slow!

		for (int i = 0; i < entitiesToLayout.length - 1; i++) {
			InternalNode sourceEntity = entitiesToLayout[i];

			double srcLocationX = tempLocationsX[i];
			double srcLocationY = tempLocationsY[i];
			double fx = forcesX[i]; // force in x direction
			double fy = forcesY[i]; // force in y direction

			for (int j = i + 1; j < entitiesToLayout.length; j++) {
				InternalNode destinationEntity = entitiesToLayout[j];

				if (!destinationEntity.equals(sourceEntity)) {
					double destLocationX = tempLocationsX[j];
					double destLocationY = tempLocationsY[j];
					double dx = srcLocationX - destLocationX;
					double dy = srcLocationY - destLocationY;
					double distance = Math.sqrt(dx * dx + dy * dy);
					double distance_sq = distance * distance;
					// make sure distance and distance squared not too small
					distance = Math.max(MIN_DISTANCE, distance);

					// If there are relationships between srcObj and destObj
					// then decrease force on srcObj (a pull) in direction of destObj
					// If no relation between srcObj and destObj then increase
					// force on srcObj (a push) from direction of destObj.
					int numRels = srcDestToNumRels[i][j];
					double avgWeight = srcDestToRelsAvgWeight[i][j];
					if (numRels > 0) {
						// nodes are pulled towards each other
						double f = sprStrain * Math.log(distance / sprLength) * numRels * avgWeight;

						fx = fx - (f * dx / distance);
						fy = fy - (f * dy / distance);

					} else {
						// nodes are repelled from each other
						//double f = Math.min(100, sprGravitation / (distance*distance));
						double f = sprGravitation / (distance_sq);
						fx = fx + (f * dx / distance);
						fy = fy + (f * dy / distance);
					}

					// According to Newton, "for every action, there is an equal
					// and opposite reaction."
					// so give the dest an opposite force
					forcesX[j] = forcesX[j] - fx;
					forcesY[j] = forcesY[j] - fy;
				}
			}

			/*
			 * //make sure forces aren't too big if (fx > 0 ) fx = Math.min(fx,
			 * 10*sprMove); else fx = Math.max(fx, -10*sprMove); if (fy > 0) fy =
			 * Math.min(fy, 10*sprMove); else fy = Math.max(fy, -10*sprMove);
			 */
			forcesX[i] = fx;
			forcesY[i] = fy;
			// Remove the src object from the list of destinations since
			// we've already calculated the force from it on all other
			// objects.
			// dests.remove(srcObj);

		}
	}

	/**
	 * Computes the position for each node in this SpringLayoutAlgorithm.
	 * The computed position will be stored in the data repository. position =
	 * position + sprMove * force
	 */
	protected void computePositions(InternalNode[] entitiesToLayout) {
		for (int i = 0; i < entitiesToLayout.length; i++) {
			if (!anchors[i] || entitiesToLayout[i].hasPreferredLocation()) {
				double oldX = tempLocationsX[i];
				double oldY = tempLocationsY[i];
				double deltaX = sprMove * forcesX[i];
				double deltaY = sprMove * forcesY[i];

				// constrain movement, so that nodes don't shoot way off to the edge
				double maxMovement = 0.2d * sprMove;
				if (deltaX >= 0) {
					deltaX = Math.min(deltaX, maxMovement);
				} else {
					deltaX = Math.max(deltaX, -maxMovement);
				}
				if (deltaY >= 0) {
					deltaY = Math.min(deltaY, maxMovement);
				} else {
					deltaY = Math.max(deltaY, -maxMovement);
				}

				largestMovement = Math.max(largestMovement, Math.abs(deltaX));
				largestMovement = Math.max(largestMovement, Math.abs(deltaY));

				double newX = oldX + deltaX;
				double newY = oldY + deltaY;
				tempLocationsX[i] = newX;
				tempLocationsY[i] = newY;
			}

		}

	}

	/**
	 * Converts the position for each node in this SpringLayoutAlgorithm
	 * to unit coordinates in double precision. The computed positions will be
	 * still stored in the data repository.
	 */
	protected void convertToUnitCoordinates(InternalNode[] entitiesToLayout) {
		double minX = Double.MAX_VALUE;
		double maxX = Double.MIN_VALUE;
		double minY = Double.MAX_VALUE;
		double maxY = Double.MIN_VALUE;
		for (int i = 0; i < entitiesToLayout.length; i++) {
			InternalNode layoutEntity = entitiesToLayout[i];
			minX = Math.min(minX, layoutEntity.getInternalX());
			minY = Math.min(minY, layoutEntity.getInternalY());
			maxX = Math.max(maxX, layoutEntity.getInternalX());
			maxY = Math.max(maxY, layoutEntity.getInternalY());
		}

		double spanX = maxX - minX;
		double spanY = maxY - minY;
		double maxSpan = Math.max(spanX, spanY);

		if (maxSpan > EPSILON) {
			for (int i = 0; i < entitiesToLayout.length; i++) {
				InternalNode layoutEntity = entitiesToLayout[i];
				double x = (layoutEntity.getInternalX() - minX) / spanX;
				double y = (layoutEntity.getInternalY() - minY) / spanY;
				tempLocationsX[i] = x;
				tempLocationsY[i] = y;
			}
		} else {
			placeRandomly(entitiesToLayout);
		}
	}

	/**
	 * Examines the number of specified relation between the <code>src</code>
	 * and the <code>dest</code> that exist in this
	 * SpringLayoutAlgorithm's relation repository.
	 * 
	 * @param src
	 *            The source part of the relaton to be examined.
	 * @param dest
	 *            The destination part of the relation to be examined.
	 * @return The number of relations between src and dest.
	 */
	private int numRelations(Object src, Object dest) {
		String key = src.toString() + dest.toString();
		Integer count = (Integer) srcDestToNumRelsMap.get(key);
		int intCount = (count == null) ? 0 : count.intValue();
		return intCount;
	}

	/**
	 * Returns the average weight between a src and dest object.
	 * 
	 * @param src
	 * @param dest
	 * @return The average weight between the given src and dest nodes
	 */
	private double avgWeight(Object src, Object dest) {
		String key = src.toString() + dest.toString();
		Double avgWeight = (Double) srcDestToRelsAvgWeightMap.get(key);
		double doubleWeight = (avgWeight == null) ? 1 : avgWeight.doubleValue();
		return doubleWeight;
	}

	protected boolean isValidConfiguration(boolean asynchronous, boolean continueous) {
		if (asynchronous && continueous)
			return true;
		else if (asynchronous && !continueous)
			return true;
		else if (!asynchronous && continueous)
			return false;
		else if (!asynchronous && !continueous)
			return true;

		return false;
	}

}
