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

import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

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
{

	private static final Logger log = Logger.getLogger(mxGraphGenerator.class.getName());

	// 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)
	{
