/**
 * Copyright (c) 2012, JGraph Ltd
 */
package com.mxgraph.analysis;

import java.util.ArrayList;

import com.mxgraph.costfunction.mxCostFunction;
import com.mxgraph.costfunction.mxDoubleValCostFunction;
import com.mxgraph.generatorfunction.mxGeneratorFunction;
import com.mxgraph.generatorfunction.mxGeneratorRandomFunction;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
import com.mxgraph.view.mxGraphView;

/**
 * @author Mate
 *
 */
public class mxGraphGenerator
{
	// cost function class that implements mxICostFunction
	//	private mxGeneratorFunction generatorFunction = new mxGeneratorRandomFunction(0,1,2);
	//	private mxGeneratorFunction generatorFunction = new mxGeneratorConstFunction(1.5);
	//	private mxGeneratorFunction generatorFunction = new mxGeneratorRandomIntFunction(0, 20);
	private mxGeneratorFunction generatorFunction = null;

	private mxCostFunction costFunction = null;

	public mxGraphGenerator(mxGeneratorFunction generatorFunction, mxCostFunction costFunction)
	{
		if (generatorFunction != null)
		{
			this.generatorFunction = generatorFunction;
		}

		if (costFunction != null)
		{
			this.costFunction = costFunction;
		}
		else
		{
			this.costFunction = new mxDoubleValCostFunction();
		}
	};

	/**
	 * @param aGraph 
	 * @param numVertexes 
	 * @return a null graph
	 */
	public void getNullGraph(mxAnalysisGraph aGraph, int numVertices)
	{
		if (numVertices < 0)
		{
			throw new IllegalArgumentException();
		}
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();

		for (int i = 0; i < numVertices; i++)
		{
			graph.insertVertex(parent, null, new Integer(i).toString(), i * 50, 0, 25, 25);
		}
	};

	/**
	 * @param aGraph
	 * @param numVertices number of vertices
	 * @return A complete graph that has <b>numVertices</b> number of vertices
	 */
	public void getCompleteGraph(mxAnalysisGraph aGraph, int numVertices)
	{
		if (numVertices < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), i * 50, 0, 25, 25);
		}

		for (int i = 0; i < numVertices; i++)
		{
			Object vertex1 = vertices[i];

			for (int j = 0; j < numVertices; j++)
			{
				Object vertex2 = vertices[j];

				if (vertex1 != vertex2 && !mxGraphStructure.areConnected(aGraph, vertex1, vertex2))
				{
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertex1, vertex2);
				}
			}
		}
	};

	/**
	 * @param aGraph
	 * @param numRows - number of rows in the grid graph
	 * @param numColumns - number of columns in the grid graph
	 * @return Returns a <b>numColumns</b> x <b>numRows</b> grid graph
	 */
	public void getGridGraph(mxAnalysisGraph aGraph, int numColumns, int numRows)
	{
		if (numColumns < 0 || numRows < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		int numVertices = numColumns * numRows;
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		int vertexCount = 0;

		for (int j = 0; j < numRows; j++)
		{
			for (int i = 0; i < numColumns; i++)
			{
				Object currVertex = vertices[vertexCount];

				if (i > 0)
				{
					// connect with previous x
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[vertexCount - 1], currVertex);
				}

				if (j > 0)
				{
					//connect with previous y
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[vertexCount - numColumns], currVertex);
				}

				vertexCount++;
			}
		}
	};

	/**
	 * Sets the physical spacing between vertices in a grid graph. This works for now only for a graph generated with mxGraphCreator.getGridGraph() only after creating the graph
	 * @param aGraph
	 * @param xSpacing - horizontal spacing between vertices
	 * @param ySpacing - vertical spacing between vertices
	 * @param numRows - number of rows in the grid graph
	 * @param numColumns - number of columns in the grid graph
	 */
	public void setGridGraphSpacing(mxAnalysisGraph aGraph, double xSpacing, double ySpacing, int numColumns, int numRows)
	{
		mxGraph graph = aGraph.getGraph();

		if (xSpacing < 0 || ySpacing < 0 || numColumns < 1 || numRows < 1)
		{
			throw new IllegalArgumentException();
		}

		Object parent = graph.getDefaultParent();
		Object[] vertices = aGraph.getChildVertices(parent);
		mxIGraphModel model = graph.getModel();

		for (int i = 0; i < numRows; i++)
		{
			for (int j = 0; j < numColumns; j++)
			{
				Object currVertex = vertices[i * numColumns + j];
				mxGeometry geometry = model.getGeometry(currVertex);
				geometry.setX(j * xSpacing);
				geometry.setY(i * ySpacing);
			}
		}
	};

	/**
	 * @param aGraph
	 * @param numVerticesGroup1 number of vertices in group 1
	 * @param numVerticesGroup2 number of vertices in group 2
	 * @return a bipartite graph with group 1 containing <b>numVerticesGroup1</b> vertices and group 2 containing <b>numVerticesGroup2</b>
	 */
	public void getBipartiteGraph(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2)
	{
		if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0)
		{
			throw new IllegalArgumentException();
		}

		int numVertices = numVerticesGroup1 + numVerticesGroup2;
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		for (int i = 0; i < numVerticesGroup1; i++)
		{
			Object currVertex = vertices[i];
			Object destVertex = vertices[getRandomInt(numVerticesGroup1, numVertices - 1)];
			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex);
		}

		for (int j = 0; j < numVerticesGroup2; j++)
		{
			Object currVertex = vertices[numVerticesGroup1 + j];
			int edgeNum = aGraph.getOpposites(aGraph.getEdges(currVertex, null, true, true, false, true), currVertex, true, true).length;

			if (edgeNum == 0)
			{
				Object destVertex = vertices[getRandomInt(0, numVerticesGroup1 - 1)];
				graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex);
			}
		}
	};

	/**
	 * Sets the physical spacing between vertices in a bipartite graph. This works for now only for a graph generated with mxGraphCreator.getBipartiteGraph() 
	 * only after creating the graph
	 * @param aGraph
	 * @param numVerticesGroup1 - number of vertices in group 1
	 * @param numVerticesGroup2 - number of vertices in group 2
	 * @param vertexSpacing - vertical spacing between vertices in the same group
	 * @param groupSpacing - spacing between groups
	 */
	public void setBipartiteGraphSpacing(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2, double vertexSpacing,
			double groupSpacing)
	{
		if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		double group1StartY = 0;
		double group2StartY = 0;
		Object parent = graph.getDefaultParent();
		mxIGraphModel model = graph.getModel();

		if (numVerticesGroup1 < numVerticesGroup2)
		{
			double centerYtimes2 = (numVerticesGroup2 * vertexSpacing);
			group1StartY = (centerYtimes2 - (numVerticesGroup1 * vertexSpacing)) / 2;
		}
		else
		{
			double centerYtimes2 = (numVerticesGroup1 * vertexSpacing);
			group2StartY = (centerYtimes2 - (numVerticesGroup2 * vertexSpacing)) / 2;
		}

		Object[] vertices = aGraph.getChildVertices(parent);

		// position vertexes for group 1
		for (int i = 0; i < numVerticesGroup1; i++)
		{
			Object currVertex = vertices[i];
			mxGeometry geometry = model.getGeometry(currVertex);
			geometry.setX(0);
			geometry.setY(group1StartY + i * vertexSpacing);
		}

		// position vertexes for group 2
		for (int i = numVerticesGroup1; i < numVerticesGroup1 + numVerticesGroup2; i++)
		{
			Object currVertex = vertices[i];
			mxGeometry geometry = model.getGeometry(currVertex);
			geometry.setX(groupSpacing);
			geometry.setY(group2StartY + (i - numVerticesGroup1) * vertexSpacing);
		}
	};

	/**
	 * @param aGraph
	 * @param numVerticesGroup1 number of vertices in group 1
	 * @param numVerticesGroup2 number of vertices in group 2
	 * @return a bipartite graph with group 1 containing <b>numVerticesGroup1</b> vertices and group 2 containing <b>numVerticesGroup2</b>
	 */
	public void getCompleteBipartiteGraph(mxAnalysisGraph aGraph, int numVerticesGroup1, int numVerticesGroup2)
	{
		if (numVerticesGroup1 < 0 || numVerticesGroup2 < 0)
		{
			throw new IllegalArgumentException();
		}

		int numVertices = numVerticesGroup1 + numVerticesGroup2;
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		for (int i = 0; i < numVerticesGroup1; i++)
		{
			for (int j = numVerticesGroup1; j < numVertices; j++)
			{
				Object currVertex = vertices[i];
				Object destVertex = vertices[j];
				graph.insertEdge(parent, null, getNewEdgeValue(aGraph), currVertex, destVertex);
			}
		}
	};

	/**
	 * @param aGraph
	 * @param xDim
	 * @param yDim
	 * @return a knight graph of size <b>xDim</b> x <b>yDim</b>
	 * Note that the minimum size is 3x3
	 */
	public void getKnightGraph(mxAnalysisGraph aGraph, int xDim, int yDim)
	{
		if (xDim < 3 || yDim < 3)
		{
			throw new IllegalArgumentException();
		}

		int numVertices = xDim * yDim;
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		//now we set up the starting conditions
		int[] currCoords = new int[2];

		//the main loop
		for (int i = 0; i < (xDim * yDim); i++)
		{
			currCoords = getVertexGridCoords(xDim, yDim, i);
			Object[] neighborMoves = getKnightMoveVertexes(aGraph, xDim, yDim, currCoords[0], currCoords[1]);

			for (int j = 0; j < neighborMoves.length; j++)
			{
				// connect current with the possible move that has minimum number of its (possible moves)
				if (!mxGraphStructure.areConnected(aGraph, vertices[i], neighborMoves[j]))
				{
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], neighborMoves[j]);
				}
				// that vertex becomes the current vertex and we repeat until no possible moves
			}
		}
	};

	/**
	 * @param aGraph
	 * @param xDim x dimension of chess-board, size starts from 1
	 * @param yDim y dimension of chess-board, size starts from 1
	 * @param xCoord x coordinate on the chess-board, coordinate starts from 1
	 * @param yCoord y coordinate on the chess-board, coordinate starts from 1
	 * @return a list of ALL vertexes which would be valid moves from the current position, regardless if they were visited or not
	 * Note that both dimensions and both coordinates must be positive
	 */
	public Object[] getKnightMoveVertexes(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord)
	{
		if (xCoord > xDim || yCoord > yDim || xDim < 1 || yDim < 1 || xCoord < 1 || yCoord < 1)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object[] vertices = aGraph.getChildVertices(graph.getDefaultParent());

		//check all possible 8 locations

		//location 1
		int currX = xCoord + 1;
		int currY = yCoord - 2;
		ArrayList<Object> possibleMoves = new ArrayList<Object>();
		// check if in bounds
		Object currVertex;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 2
		currX = xCoord + 2;
		currY = yCoord - 1;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 3
		currX = xCoord + 2;
		currY = yCoord + 1;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 4
		currX = xCoord + 1;
		currY = yCoord + 2;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 5
		currX = xCoord - 1;
		currY = yCoord + 2;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 6
		currX = xCoord - 2;
		currY = yCoord + 1;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 7
		currX = xCoord - 2;
		currY = yCoord - 1;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 8
		currX = xCoord - 1;
		currY = yCoord - 2;

		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		return possibleMoves.toArray();
	};

	/**
	 * use this only with the grid graph, and various chess-board graphs, because of vertex ordering
	 * @param xDim x dimension of chess-board, size starts from 1
	 * @param yDim y dimension of chess-board, size starts from 1
	 * @param value value of the vertex that needs coordinates returned
	 * @return int[x,y] where x and y are the coordinates in the grid or chess-board
	 * Note that both dimensions must be positive
	 */
	public int[] getVertexGridCoords(int xDim, int yDim, int value)
	{
		if (value > ((yDim * xDim) - 1) || xDim < 0 || yDim < 0 || value < 0)
		{
			throw new IllegalArgumentException();
		}

		int yCoord = (int) Math.floor(value / xDim);
		int xCoord = (value - yCoord * xDim) + 1;
		yCoord += 1;

		int[] coords = new int[2];
		coords[0] = xCoord;
		coords[1] = yCoord;
		return coords;
	};

	/**
	 * use this only with the grid graph and various chess-board graphs, because of vertex ordering
	 * @param vertices
	 * @param xDim x dimension of chess-board, size starts from 1
	 * @param yDim y dimension of chess-board, size starts from 1
	 * @param xCoord x coordinate on the chess-board, coordinate starts from 1
	 * @param yCoord y coordinate on the chess-board, coordinate starts from 1
	 * @return vertex on the desired coordinates.
	 * Note that both dimensions and both coordinates must be positive
	 */
	private Object getVertexFromGrid(Object[] vertices, int xDim, int yDim, int xCoord, int yCoord)
	{
		if (xCoord > xDim || yCoord > yDim || xDim < 1 || yDim < 1 || xCoord < 1 || yCoord < 1)
		{
			throw new IllegalArgumentException();
		}

		int value = (yCoord - 1) * xDim + xCoord - 1;

		return vertices[value];
	};

	/**
	 * @param xDim
	 * @param yDim
	 * @param weights
	 * Return a king graph of size <b>xDim</b> x <b>yDim</b>
	 * Note that the minimum size is 4x4
	 */
	public void getKingGraph(mxAnalysisGraph aGraph, int xDim, int yDim)
	{
		if (xDim < 2 || yDim < 2)
		{
			throw new IllegalArgumentException();
		}

		int numVertices = xDim * yDim;
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		//now we set up the starting conditions
		int[] currCoords = new int[2];

		//the main loop
		for (int i = 0; i < (xDim * yDim); i++)
		{
			currCoords = getVertexGridCoords(xDim, yDim, i);
			Object[] neighborMoves = getKingMoveVertexes(aGraph, xDim, yDim, currCoords[0], currCoords[1]);

			for (int j = 0; j < neighborMoves.length; j++)
			{
				// connect current with the possible move that has minimum number of its (possible moves)
				if (!mxGraphStructure.areConnected(aGraph, vertices[i], neighborMoves[j]))
				{
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], neighborMoves[j]);
				}
				// that vertex becomes the current vertex and we repeat until no possible moves
			}
		}
	};

	/**
	 * @param aGraph
	 * @param xDim x dimension of the chessboard
	 * @param yDim y dimension of the chessboard
	 * @param xCoord the current x position of the king
	 * @param yCoord the current y position of the king
	 * @return list of all possible moves of a king from the specified position
	 * Note that both dimensions and both coordinates must be positive
	 */
	public Object[] getKingMoveVertexes(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord)
	{
		if (xDim < 0 || yDim < 0 || xCoord < 0 || yCoord < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object[] vertices = aGraph.getChildVertices(graph.getDefaultParent());

		//check all possible 8 locations

		//location 1
		int currX = xCoord + 1;
		int currY = yCoord - 1;
		ArrayList<Object> possibleMoves = new ArrayList<Object>();
		// check if in bounds
		Object currVertex;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 2
		currX = xCoord + 1;
		currY = yCoord;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 3
		currX = xCoord + 1;
		currY = yCoord + 1;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 4
		currX = xCoord;
		currY = yCoord + 1;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 5
		currX = xCoord - 1;
		currY = yCoord + 1;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 6
		currX = xCoord - 1;
		currY = yCoord;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 7
		currX = xCoord - 1;
		currY = yCoord + 1;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		//location 8
		currX = xCoord;
		currY = yCoord - 1;
		// check if in bounds
		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			currVertex = getVertexFromGrid(vertices, xDim, yDim, currX, currY);
			possibleMoves.add(currVertex);
		}

		return possibleMoves.toArray();
	};

	/**
	 * @param aGraph
	 * Returns a Petersen graph
	 */
	public void getPetersenGraph(mxAnalysisGraph aGraph)
	{
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[10];
		
		for (int i = 0; i < 10; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[2]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[8]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[0], vertices[9]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[2]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[5]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[1], vertices[7]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[2], vertices[4]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[4]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[7]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[3], vertices[9]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[4], vertices[6]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[5], vertices[6]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[5], vertices[9]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[6], vertices[8]);
		graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[7], vertices[8]);
	};

	/**
	 * @param aGraph
	 * @param numVertices
	 * Returns a path graph
	 */
	public void getPathGraph(mxAnalysisGraph aGraph, int numVertices)
	{
		if (numVertices < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		for (int i = 0; i < numVertices - 1; i++)
		{
			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[i + 1]);
		}
	};

	/**
	 * Sets the physical spacing between vertices in a path graph. This works for now only for a graph generated with mxGraphCreator.getPathGraph() 
	 * only after creating the graph
	 * @param aGraph
	 * @param spacing
	 */
	public void setPathGraphSpacing(mxAnalysisGraph aGraph, double spacing)
	{
		if (spacing < 0)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = aGraph.getChildVertices(parent);
		mxIGraphModel model = graph.getModel();

		for (int i = 0; i < vertices.length; i++)
		{
			Object currVertex = vertices[i];

			mxGeometry geometry = model.getGeometry(currVertex);
			geometry.setX(0);
			geometry.setY(i * spacing);
		}
	};

	/**
	 * @param aGraph
	 * @param numVertices
	 * Returns a star graph
	 * Note that minimum vertex number is 4
	 */
	public void getStarGraph(mxAnalysisGraph aGraph, int numVertices)
	{
		if (numVertices < 4)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		int numVertexesInPerimeter = numVertices - 1;

		for (int i = 0; i < numVertexesInPerimeter; i++)
		{
			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[numVertexesInPerimeter], vertices[i]);
		}
	};

	/**
	 * Sets the physical size of a star graph. This works for now only for a graph generated with mxGraphCreator.getStarGraph() and getWheelGraph()
	 * @param aGraph
	 * @param graphSize
	 */
	public void setStarGraphLayout(mxAnalysisGraph aGraph, double graphSize)
	{
		if (graphSize < 4)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = aGraph.getChildVertices(parent);
		mxIGraphModel model = graph.getModel();
		int vertexNum = vertices.length;
		double centerX = graphSize / 2f;
		double centerY = centerX;

		int numVertexesInPerimeter = vertexNum - 1;

		//create the circle
		for (int i = 0; i < numVertexesInPerimeter; i++)
		{
			//calc the position
			double x = 0;
			double y = 0;
			double currRatio = ((double) i / (double) numVertexesInPerimeter);
			currRatio = currRatio * 2;
			currRatio = currRatio * (double) Math.PI;
			x = Math.round(centerX + Math.round(graphSize * Math.sin(currRatio) / 2));
			y = Math.round(centerY - Math.round(graphSize * Math.cos(currRatio) / 2));
			Object currVertex = vertices[i];
			mxGeometry geometry = model.getGeometry(currVertex);
			geometry.setX(x);
			geometry.setY(y);
		}

		mxGeometry geometry = model.getGeometry(vertices[vertexNum - 1]);
		geometry.setX(centerX);
		geometry.setY(centerY);
	};

	/**
	 * @param aGraph
	 * @param numVertices
	 * Returns a wheel graph. Note that numVertices has to be at least 4.
	 */
	public void getWheelGraph(mxAnalysisGraph aGraph, int numVertices)
	{
		if (numVertices < 4)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numVertices];
		
		for (int i = 0; i < numVertices; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		int numVerticesInPerimeter = numVertices - 1;

		for (int i = 0; i < numVerticesInPerimeter; i++)
		{
			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[numVerticesInPerimeter], vertices[i]);

			if (i < numVerticesInPerimeter - 1)
			{
				graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[i + 1]);
			}
			else
			{
				graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertices[i], vertices[0]);
			}
		}
	};

	/**
	 * @param aGraph
	 * @param numBranches number of branches (minimum >= 2)
	 * @param branchSize number of vertices in a single branch (minimum >= 2)
	 * Returns a friendship windmill graph (aka Dutch windmill)
	 */
	public void getFriendshipWindmillGraph(mxAnalysisGraph aGraph, int numBranches, int branchSize)
	{
		if (numBranches < 2 || branchSize < 2)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		int numVertices = numBranches * branchSize + 1;
		Object[] vertices = new Object[numVertices];
		int vertexCount = 0;

		for (int i = 0; i < numBranches; i++)
		{
			for (int j = 0; j < branchSize; j++)
			{
				vertices[vertexCount] = graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25);
				vertexCount++;
			}
		}

		vertices[numVertices - 1] = graph.insertVertex(parent, null, new Integer(numVertices - 1).toString(), 0, 0, 25, 25);

		//make the connections
		for (int i = 0; i < numBranches; i++)
		{
			Object oldVertex = vertices[numVertices - 1];

			for (int j = 0; j < branchSize; j++)
			{
				Object currVertex = vertices[i * (branchSize) + j];
				graph.insertEdge(parent, null, getNewEdgeValue(aGraph), oldVertex, currVertex);
				oldVertex = currVertex;
			}

			Object currVertex = vertices[numVertices - 1];
			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), oldVertex, currVertex);
		}
	};

	/**
	 * @param aGraph
	 * @param numBranches - number of branches (minimum >= 2)
	 * @param branchSize - number of vertices in a single branch (minimum >= 2)
	 * Returns a windmill graph
	 */
	public void getWindmillGraph(mxAnalysisGraph aGraph, int numBranches, int branchSize)
	{
		if (numBranches < 2 || branchSize < 2)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		int numVertices = numBranches * branchSize + 1;
		Object[] vertices = new Object[numVertices];
		
		int vertexCount = 0;

		for (int i = 0; i < numBranches; i++)
		{
			for (int j = 0; j < branchSize; j++)
			{
				vertices[vertexCount] = graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25);
				vertexCount++;
			}
		}

		vertices[numVertices - 1] = graph.insertVertex(parent, null, new Integer(numVertices - 1).toString(), 0, 0, 25, 25);
		Object centerVertex = vertices[numVertices - 1];

		//make the connections
		for (int i = 0; i < numBranches; i++)
		{
			for (int j = 0; j < branchSize; j++)
			{
				Object vertex1 = vertices[i * (branchSize) + j];
				if (!mxGraphStructure.areConnected(aGraph, centerVertex, vertex1))
				{
					graph.insertEdge(parent, null, getNewEdgeValue(aGraph), centerVertex, vertex1);
				}

				for (int k = 0; k < branchSize; k++)
				{
					Object vertex2 = vertices[i * (branchSize) + k];

					if (j != k && !mxGraphStructure.areConnected(aGraph, vertex1, vertex2))
					{
						graph.insertEdge(parent, null, getNewEdgeValue(aGraph), vertex1, vertex2);
					}
				}
			}
		}
	};

	//NOTE the inside code is delicate, so please change it only if you know what you're doing
	/**
	 * Sets the layout of a windmill graph. Use this method only for graphs generated with mxGraphGenerator.getWindmillGraph() and getFriendshitWindmillGraph() 
	 * @param aGraph
	 * @param numBranches
	 * @param numVerticesInBranch
	 * @param graphSize
	 */
	public void setWindmillGraphLayout(mxAnalysisGraph aGraph, int numBranches, int numVerticesInBranch, double graphSize)
	{
		if (graphSize < 0 || numBranches < 2 || numVerticesInBranch < 1)
		{
			throw new IllegalArgumentException();
		}

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = aGraph.getChildVertices(parent);
		mxIGraphModel model = graph.getModel();
		int vertexNum = vertices.length;
		double centerX = graphSize / 2f;
		double centerY = centerX;
		boolean isBranchSizeEven = ((numVerticesInBranch) % 2 == 0);
		int middleIndex = (int) Math.ceil(numVerticesInBranch / 2f);

		//create the circle
		for (int i = 0; i < numBranches; i++)
		{
			for (int j = 0; j < numVerticesInBranch; j++)
			{
				double currSize = this.getRingSize(j + 1, numVerticesInBranch, graphSize);

				//calc the position
				double x = 0;
				double y = 0;
				int numVertexesInPerimeter = 0;
				double currRatio = 0;
				numVertexesInPerimeter = numBranches;

				// need to detect the 2 middle vertices for even sized branches
				if (isBranchSizeEven && j == middleIndex - 1)
				{
					//full size
					currRatio = ((double) i - (0.0005f * graphSize / Math.pow(numVerticesInBranch, 1))) / (double) numVertexesInPerimeter;
				}
				else if (isBranchSizeEven && j == middleIndex)
				{
					currRatio = ((double) i + (0.0005f * graphSize / Math.pow(numVerticesInBranch, 1))) / (double) numVertexesInPerimeter;
				}
				else if (!isBranchSizeEven && currSize == graphSize)
				{
					currRatio = ((double) i / (double) numVertexesInPerimeter);
				}
				else
				{
					if (j + 1 < middleIndex)
					{
						//before middle
						currRatio = ((double) (i - 1f / Math.pow(currSize, 0.25) + 0.00000000000015f * Math.pow(currSize, 4)) / (double) numVertexesInPerimeter);
					}
					else
					{
						//after middle
						currRatio = ((double) (i + 1f / Math.pow(currSize, 0.25) - 0.00000000000015f * Math.pow(currSize, 4)) / (double) numVertexesInPerimeter);
					}
				}

				currRatio = currRatio * 2;
				currRatio = currRatio * (double) Math.PI;
				x = Math.round(centerX + Math.round(currSize * Math.sin(currRatio) / 2));
				y = Math.round(centerY - Math.round(currSize * Math.cos(currRatio) / 2));
				//shoot
				int currIndex = i * (numVerticesInBranch) + j;
				Object currVertex = vertices[currIndex];
				mxGeometry geometry = model.getGeometry(currVertex);
				geometry.setX(x);
				geometry.setY(y);
			}
		}

		//the center vertex is the last one
		Object currVertex = vertices[vertexNum - 1];
		mxGeometry geometry = model.getGeometry(currVertex);
		geometry.setX(centerX);
		geometry.setY(centerY);
	};

	/**
	 * A helper function that calculates the ring size for a windmill graph, based on the index of a vertex in a brach and branch size. - for internal use
	 * @param currVertex - starting from 1
	 * @param branchSize - starting from 1
	 * @param fullSize
	 * @return ring size
	 */
	private double getRingSize(int currIndex, int branchSize, double fullSize)
	{
		if (currIndex < 1 || currIndex > branchSize || branchSize < 1 || fullSize < 0)
		{
			throw new IllegalArgumentException();
		}

		int middleIndex = 0;
		boolean isBranchSizeEven = ((branchSize) % 2 == 0);

		middleIndex = (int) Math.ceil(branchSize / 2f);

		if (currIndex == middleIndex || (isBranchSizeEven && currIndex == middleIndex + 1))
		{
			//full size
			return fullSize;
		}
		else if (currIndex >= middleIndex)
		{
			//after middle
			currIndex = branchSize - currIndex + 1;
		}
		return (((float) Math.pow(currIndex, 0.75) / (float) Math.pow(middleIndex, 0.75)) * fullSize);
	};

	/**
	 * Generates a random graph
	 * @param aGraph
	 * @param numNodes number of vertexes
	 * @param numEdges number of edges (may be inaccurate if <b>forceConnected</b> is set to true
	 * @param allowSelfLoops if true, there will be a chance that self loops will be generated too
	 * @param allowMultipleEdges if true, there will be a chance that multiple edges will be generated (multiple edges between the same two vertices)
	 * @param forceConnected if true the resulting graph will be always connected, but this may alter <b>numEdges</b>
	 */
	public void getSimpleRandomGraph(mxAnalysisGraph aGraph, int numNodes, int numEdges, boolean allowSelfLoops,
			boolean allowMultipleEdges, boolean forceConnected)
	{
		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		Object[] vertices = new Object[numNodes];
		
		for (int i = 0; i < numNodes; i++)
		{
			vertices[i] = graph.insertVertex(parent, null, new Integer(i).toString(), 0, 0, 25, 25);
		}

		for (int i = 0; i < numEdges; i++)
		{
			boolean goodPair = true;
			Object startVertex;
			Object endVertex;

			do
			{
				goodPair = true;
				startVertex = vertices[(int) Math.round(Math.random() * (vertices.length - 1))];
				endVertex = vertices[(int) Math.round(Math.random() * (vertices.length - 1))];

				if (!allowSelfLoops && startVertex.equals(endVertex))
				{
					goodPair = false;
				}
				else if (!allowMultipleEdges && mxGraphStructure.areConnected(aGraph, startVertex, endVertex))
				{
					goodPair = false;
				}
			}
			while (!goodPair);

			graph.insertEdge(parent, null, getNewEdgeValue(aGraph), startVertex, endVertex);
		}

		if (forceConnected)
		{
			mxGraphStructure.makeConnected(aGraph);
		}
	};

	/**
	 * Generates a random tree graph
	 * @param aGraph
	 * @param vertexCount
	 */
	public void getSimpleRandomTree(mxAnalysisGraph aGraph, int vertexCount)
	{
		int edgeCount = (int) Math.round(vertexCount * 2);
		this.getSimpleRandomGraph(aGraph, vertexCount, edgeCount, false, false, true);

		//still need to remove surplus edges
		Object[] vertices = aGraph.getChildVertices(aGraph.getGraph().getDefaultParent());

		try
		{
			oneSpanningTree(aGraph, true, true);
		}
		catch (StructuralException e)
		{
			System.out.println(e);
		}

		try
		{
			mxGraphStructure.makeTreeDirected(aGraph, vertices[(int) Math.round(Math.random() * (vertices.length - 1))]);
		}
		catch (StructuralException e)
		{
			System.out.println(e);
		}
	};

	/**
	 * Creates a new edge value based on graph properties in mxAnalysisGraph. Used mostly when creating new edges during graph generation.
	 * @param aGraph
	 * @return
	 */
	public Double getNewEdgeValue(mxAnalysisGraph aGraph)
	{
		if (getGeneratorFunction() != null)
		{
			mxGraph graph = aGraph.getGraph();
			return getGeneratorFunction().getCost(graph.getView().getState(graph.getDefaultParent()));
		}
		else
		{
			return null;
		}
	};

	/**
	 * @param graph
	 * @param weighted if true, the edges will be weighted, otherwise all will have default value (1.0)
	 * @param minWeight minimum edge weight if weighted
	 * @param maxWeight maximum edge weight if weighted
	 * @return a generator function
	 */
	public static mxGeneratorFunction getGeneratorFunction(mxGraph graph, boolean weighted, double minWeight, double maxWeight)
	{
		if (weighted)
		{
			return new mxGeneratorRandomFunction(minWeight, maxWeight, 2);
		}
		else
		{
			return null;
		}
	};

	public mxGeneratorFunction getGeneratorFunction()
	{
		return this.generatorFunction;
	};

	/**
	 * @param minValue
	 * @param maxValue
	 * @return a random integer in the interval [minValue, maxValue]
	 */
	public int getRandomInt(int minValue, int maxValue)
	{
		if (minValue == maxValue)
			return minValue;
		if (minValue > maxValue)
		{
			int tmp = maxValue;
			maxValue = minValue;
			minValue = tmp;
		}

		int currValue = 0;
		currValue = minValue + (int) Math.round((Math.random() * (maxValue - minValue)));
		return currValue;
	};

	/**
	 * @param graph
	 * @param forceConnected if true, an unconnected graph is made connected
	 * @param forceSimple if true, a non-simple graph is made simple
	 * Calculates one spanning tree of graph, which doesn't have to be but can be minimal
	 * (this is faster than minimal spanning tree, so if you need any spanning tree, use this one)
	 * Self loops and multiple edges are automatically removed!
	 * Also, unconnected graphs are made connected!
	 * @throws StructuralException the graph has to be simple (no self-loops and no multiple edges) 
	 */
	public void oneSpanningTree(mxAnalysisGraph aGraph, boolean forceConnected, boolean forceSimple) throws StructuralException
	{
		mxGraph graph = aGraph.getGraph();

		boolean isSimple = mxGraphStructure.isSimple(aGraph);
		boolean isConnected = mxGraphStructure.isConnected(aGraph);

		if (!isSimple)
		{
			if (forceSimple)
			{
				mxGraphStructure.makeSimple(aGraph);
			}
			else
			{
				throw new StructuralException("Graph is not simple.");
			}
		}

		if (!isConnected)
		{
			if (forceConnected)
			{
				mxGraphStructure.makeConnected(aGraph);
			}
			else
			{
				throw new StructuralException("Graph is not connected.");
			}
		}

		Object[] edges = aGraph.getChildEdges(graph.getDefaultParent());
		int edgeCount = edges.length;

		for (int i = 0; i < edgeCount; i++)
		{
			Object currEdge = edges[i];
			graph.removeCells(new Object[] { currEdge });

			if (!mxGraphStructure.isConnected(aGraph))
			{
				graph.addCell(currEdge);
			}
		}
	};

	//TODO make a double check to avoid unnecessary cases of throwing an exception (if the algorithm can't work out a solution, try a mirrored strategy)
	/**
	 * @param aGraph
	 * @param xDim x dimension of the chessboard
	 * @param yDim y dimension of the chessboard
	 * @param startVertexValue vertex where the tour will start
	 * @throws StructuralException not all size combinations are allowed, see wikipedia for a more detailed explanation
	 * Returns a Knight's Tour graph
	 */
	public void getKnightTour(mxAnalysisGraph aGraph, int xDim, int yDim, int startVertexValue) throws StructuralException
	{
		if (xDim < 5 || yDim < 5)
		{
			throw new IllegalArgumentException();
		}

		ArrayList<Object> resultPath = new ArrayList<Object>();
		int vertexNum = xDim * yDim;

		mxGraph graph = aGraph.getGraph();
		Object parent = graph.getDefaultParent();
		int vertexCount = 0;

		for (int i = 0; i < vertexNum; i++)
		{
			graph.insertVertex(parent, null, new Integer(vertexCount).toString(), 0, 0, 25, 25);
			vertexCount++;
		}

		// we have the board set up
		Object[] vertices = aGraph.getChildVertices(parent);

		//now we set up the starting conditions
		int currValue = startVertexValue;
		int[] currCoords = new int[2];
		Object oldMove = vertices[startVertexValue];
		currCoords = getVertexGridCoords(xDim, yDim, startVertexValue);
		resultPath.add(oldMove);
		Object nextMove = getNextKnightMove(aGraph, xDim, yDim, currCoords[0], currCoords[1], resultPath);
		mxCostFunction costFunction = aGraph.getGenerator().getCostFunction();
		mxGraphView view = graph.getView();
		
		//the main loop
		while (nextMove != null)
		{
			// connect current with the possible move that has minimum number of its (possible moves)
			graph.insertEdge(parent, null, null, oldMove, nextMove);
			resultPath.add(nextMove);
			// that vertex becomes the current vertex and we repeat until no possible moves
			
			currValue = (int) costFunction.getCost(new mxCellState(view, nextMove, null));
			currCoords = getVertexGridCoords(xDim, yDim, currValue);
			oldMove = nextMove;
			nextMove = getNextKnightMove(aGraph, xDim, yDim, currCoords[0], currCoords[1], resultPath);
		}

		if (resultPath.size() < vertexNum)
		{
			//the mirrored strategy should go here, instead of the exception
			//and the exception would be thrown only if the mirrored fails too
			throw new StructuralException("Could not generate a correct Knight tour with size " + xDim + " x " + yDim + ".");
		}
	};

	/**
	 * Helper function for Knights Tour - for internal use
	 * @param aGraph
	 * @param xDim
	 * @param yDim
	 * @param xCoord
	 * @param yCoord
	 * @param resultPath
	 * @return
	 */
	private Object getNextKnightMove(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord, ArrayList<Object> resultPath)
	{
		Object[] possibleMoves = getKnightMoveVertexes(aGraph, xDim, yDim, xCoord, yCoord);
		//get the position with minimum possible moves
		int minMoveNum = 9;
		float biggestDistance = 0;
		Object currVertex = null;
		mxCostFunction costFunction = aGraph.getGenerator().getCostFunction();
		mxGraphView view = aGraph.getGraph().getView();
		
		for (int i = 0; i < possibleMoves.length; i++)
		{
			int currValue = (int) costFunction.getCost(new mxCellState(view, possibleMoves[i], null));
			int[] currCoords = getVertexGridCoords(xDim, yDim, currValue);
			int currMoveNum = getPossibleKnightMoveCount(aGraph, xDim, yDim, currCoords[0], currCoords[1]);
			float currDistance = getDistanceFromGridCenter(xDim, yDim, currValue);

			if ((currMoveNum < minMoveNum || (currMoveNum == minMoveNum && currDistance > biggestDistance))
					&& !resultPath.contains(possibleMoves[i]))
			{
				biggestDistance = currDistance;
				minMoveNum = currMoveNum;
				currVertex = possibleMoves[i];
			}
		}
		return currVertex;
	};

	/**
	 * Helper function for Knights Tour - for internal use
	 * @param aGraph
	 * @param xDim
	 * @param yDim
	 * @param xCoord
	 * @param yCoord
	 * @return
	 */
	private int getPossibleKnightMoveCount(mxAnalysisGraph aGraph, int xDim, int yDim, int xCoord, int yCoord)
	{
		//check all possible 8 locations

		//location 1
		int currX = xCoord + 1;
		int currY = yCoord - 2;
		int possibleMoveCount = 0;
		Object parent = aGraph.getGraph().getDefaultParent();
		Object[] vertices = aGraph.getChildVertices(parent);

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 2
		currX = xCoord + 2;
		currY = yCoord - 1;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 3
		currX = xCoord + 2;
		currY = yCoord + 1;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 4
		currX = xCoord + 1;
		currY = yCoord + 2;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 5
		currX = xCoord - 1;
		currY = yCoord + 2;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 6
		currX = xCoord - 2;
		currY = yCoord + 1;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 7
		currX = xCoord - 2;
		currY = yCoord - 1;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		//location 8
		currX = xCoord - 1;
		currY = yCoord - 2;

		if (currX > 0 && currX <= xDim && currY > 0 && currY <= yDim)
		{
			if (aGraph.getEdges(getVertexFromGrid(vertices, xDim, yDim, currX, currY), parent, false, true).length == 0)
			{
				possibleMoveCount++;
			}
		}

		return possibleMoveCount;
	};

	/**
	 * Helper function for Knights Tour - for internal use
	 * @param xDim
	 * @param yDim
	 * @param currValue
	 * @return
	 */
	private float getDistanceFromGridCenter(int xDim, int yDim, int currValue)
	{
		float centerX = (xDim + 1) / 2f;
		float centerY = (yDim + 1) / 2f;
		int[] currCoords = getVertexGridCoords(xDim, yDim, currValue);
		float x = Math.abs(centerX - currCoords[0]);
		float y = Math.abs(centerY - currCoords[1]);

		return (float) Math.sqrt(x * x + y * y);
	}

	public mxCostFunction getCostFunction()
	{
		return costFunction;
	}

	public void setCostFunction(mxCostFunction costFunction)
	{
		this.costFunction = costFunction;
	};
};
