package net.returnvoid.graphics.grid;

import net.returnvoid.tools.RMath;

/*
 * This code is copyright (c) Diana Lange 2017
 *
 * The library is published under the Creative Commons license NonCommercial 4.0.
 * Please check https://creativecommons.org/licenses/by-nc/4.0/ for more information.
 * 
 * This program is distributed in the hope that it will be useful, but without any warranty.
 */

/**
 * A grid factory class to help setting up grids easily.
 * 
 * @author Diana Lange
 *
 */
public class GridMaker {

	/**
	 * No instances allowed.
	 */
	private GridMaker() {
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Spiral |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (0, 0), with a radius
	 * of 100 and one rotation. The elements will be separated by PI * 0.1
	 * radian. You can change these settings after the grid has been built.
	 * Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral().setX(200).setY(100);
	 * </pre>
	 * 
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral() {

		return GridMaker.createSpiral(0, 0, RMath.PI * 0.1f);
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with a
	 * radius of 100 and one rotation. The elements will be separated by PI *
	 * 0.1 radian. You can change these settings after the grid has been built.
	 * Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral(200, 100).setRotations(5);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral(float x, float y) {

		return GridMaker.createSpiral(x, y, RMath.PI * 0.1f);
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with a
	 * radius of 100 and one rotation. The elements will be separated by
	 * <b>angle</b> radian. You can change these settings after the grid has
	 * been built. Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral(200, 100, PI / 4).setRotations(5);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param angle
	 *            The angle distance for each element (in radians).
	 * 
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral(float x, float y, float angle) {

		Float maxRadius = 100f;
		Float minRadius = 0f;
		Float startAngle = 0f;

		Float rotations = 1f;
		Integer dir = 1;

		SpiralGrid grid = new SpiralGrid(x, y, angle, startAngle, rotations, dir, minRadius, maxRadius);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with a
	 * radius of 100 and <b>rotations</b> (number of rotations). The elements
	 * will be separated by <b>angle</b> radian. You can change these settings
	 * after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral(200, 100, PI / 4, 5).setEndRadius(300);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param angle
	 *            The angle distance for each element (in radians).
	 * @param rotations
	 *            Sets the number of rotations the spiral should make.
	 * 
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral(float x, float y, float angle, float rotations) {

		Float maxRadius = 100f;
		Float minRadius = 0f;
		Float startAngle = 0f;

		Integer dir = 1;

		SpiralGrid grid = new SpiralGrid(x, y, angle, startAngle, rotations, dir, minRadius, maxRadius);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with a
	 * radius of 100 and <b>rotations</b> (number of rotations). The elements
	 * will be separated by <b>angle</b> radian. You can change these settings
	 * after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral(200, 100, PI / 4, 5).setEndRadius(300);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid.
	 * @param radius
	 *            The outer radius of the spiral.
	 * @param angle
	 *            The angle distance for each element (in radians).
	 * @param rotations
	 *            Sets the number of rotations the spiral should make.
	 * 
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral(float x, float y, float radius, float angle, float rotations) {

		Float minRadius = 0f;
		Float startAngle = 0f;

		Integer dir = 1;

		SpiralGrid grid = new SpiralGrid(x, y, angle, startAngle, rotations, dir, minRadius, radius);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral. The elements will be distributed not visually evenly. This means,
	 * the elements will be closer to each other in the center of the spiral
	 * than the ones at the bounds. <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with a
	 * radius of <b>endRadius</b> and <b>rotations</b> (number of rotations).
	 * There will be <b>size</b> elements positionen on this grid. You can
	 * change these settings after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * SpiralGrid grid = GridMaker.createSpiral(200, 100, 20, 5, 300);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param size
	 *            Sets the number of elements which will be placed on the grid.
	 * @param rotations
	 *            Sets the number of rotations the spiral should make.
	 * @param endRadius
	 *            Sets the radius where the spiral ends. The last element of
	 *            this grid will be positioned on that radius.
	 * @return The new spiral grid.
	 */
	static public SpiralGrid createSpiral(float x, float y, int size, float rotations, float endRadius) {

		float minRadius = 0f;
		float startAngle = 0f;
		float angleSteps = RMath.PI * 0.1f;

		Integer dir = 1;

		SpiralGrid grid = new SpiralGrid(x, y, angleSteps, startAngle, rotations, dir, minRadius, endRadius);
		return grid.setSize(size);
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Ellipse |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Creates an ellipsoid grid. The elements of this grid will be located on
	 * an ellipse with a given radius. The elements will be distributed evenly.
	 * <br>
	 * This method sets up a basic ellipsoid grid at location (0, 0), with a
	 * radius of 100 and 20 elements. You can change these settings after the
	 * grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EllipseGrid grid = GridMaker.createEllipse().setX(200).setY(100);
	 * </pre>
	 * 
	 * @return The new ellipsoid grid.
	 */
	static public EllipseGrid createEllipse() {

		EllipseGrid grid = new EllipseGrid(0, 0, 100, 100, 20, 0f);
		return grid;
	}

	/**
	 * Creates an ellipsoid grid. The elements of this grid will be located on
	 * an ellipse with a given radius. The elements will be distributed evenly.
	 * <br>
	 * This method sets up a basic ellipsoid grid at location (<b>x, y</b>),
	 * with a radius of 100 and 20 elements. You can change these settings after
	 * the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EllipseGrid grid = GridMaker.createEllipse(200, 100).setRadius(200);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @return The new ellipsoid grid.
	 */
	static public EllipseGrid createEllipse(float x, float y) {

		EllipseGrid grid = new EllipseGrid(x, y, 100, 100, 20, 0f);
		return grid;
	}

	/**
	 * Creates an ellipsoid grid. The elements of this grid will be located on
	 * an ellipse with a given radius. The elements will be distributed evenly.
	 * <br>
	 * This method sets up a basic ellipsoid grid at location (<b>x, y</b>),
	 * with <b>radius</b> and 20 elements. You can change these settings after
	 * the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EllipseGrid grid = GridMaker.createEllipse(200, 100, 50.5).setSize(50);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param radius
	 *            Sets the radius for this grid. The width and height of the
	 *            grid will be equal.
	 * @return The new ellipsoid grid.
	 */
	static public EllipseGrid createEllipse(float x, float y, float radius) {

		EllipseGrid grid = new EllipseGrid(x, y, radius, radius, 20, 0f);
		return grid;
	}

	/**
	 * Creates an ellipsoid grid. The elements of this grid will be located on
	 * an ellipse with a given radius. The elements will be distributed evenly.
	 * <br>
	 * This method sets up a basic ellipsoid grid at location (<b>x, y</b>),
	 * with <b>radius</b> and <b>size</b> elements. You can change these
	 * settings after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EllipseGrid grid = GridMaker.createEllipse(200, 100, 50.5, 50)
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param radius
	 *            Sets the radius for this grid. The width and height of the
	 *            grid will be equal.
	 * @param size
	 *            The number of elements for this grid (>= 1).
	 * @return The new ellipsoid grid.
	 */
	static public EllipseGrid createEllipse(float x, float y, float radius, int size) {

		EllipseGrid grid = new EllipseGrid(x, y, radius, radius, size, 0f);
		return grid;
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Table |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Creates a table grid (a grid that is defined by rows and columns). The
	 * elements will be distributed evenly.<br>
	 * This method sets up a basic table grid at location (0, 0), with 100
	 * width, 100 height and 100 elements. You can change these settings after
	 * the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * TableGrid grid = GridMaker.createTable().setX(200).setY(100);
	 * </pre>
	 * 
	 * @return The new table grid.
	 */
	static public TableGrid createTable() {

		TableGrid grid = new TableGrid(0, 0, 100, 100, 10, 10, 0, 0);
		return grid;
	}

	/**
	 * Creates a table grid (a grid that is defined by rows and columns). The
	 * elements will be distributed evenly.<br>
	 * This method sets up a basic table grid at location (0, 0), with <b>w</b>
	 * width, <b>h</b> height and approx. <b>size</b> elements. The <b>size</b>
	 * is just an approximation because this method tries to create quadratic
	 * grid elements rather then rectangles. But depending on the width and
	 * height of the grid, there might be no solution which will match the exact
	 * given <b>size</b>. You can change these settings after the grid has been
	 * built. Example:<br>
	 * 
	 * <pre>
	 * TableGrid grid = GridMaker.createTable(100, 200, 344).setX(200).setY(100);
	 * </pre>
	 * 
	 * @param w
	 *            The width of the grid.
	 * @param h
	 *            The height of the grid.
	 * @param size
	 *            The number of elements for this grid (>= 1).
	 * 
	 * @return The new table grid.
	 */
	static public TableGrid createTable(float w, float h, int size) {

		boolean wh = w > h;

		float aspectRatio = wh ? w / h : h / w;

		int numX = 0;
		int numY = 0;

		if (w == h) {
			numX = numY = (int) (Math.pow(size * aspectRatio, 0.5));
		} else if (wh) {
			numX = (int) (Math.pow(size * aspectRatio, 0.5));
			numY = (int) (size / (float) numX);
		} else {
			numY = (int) (Math.pow(size * aspectRatio, 0.5));
			numX = (int) (size / (float) numY);
		}

		if (numX < 1) {
			numX = 1;
		}

		if (numY < 1) {
			numY = 1;
		}

		TableGrid grid = new TableGrid(0, 0, w, h, numX, numY, 0, 0);
		return grid;
	}

	/**
	 * Creates a table grid (a grid that is defined by rows and columns). The
	 * elements will be distributed evenly.<br>
	 * This method sets up a basic table grid at location (0, 0), with <b>w</b>
	 * width, <b>h</b> and <b>columns * rows</b> elements. You can change these
	 * settings after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * TableGrid grid = GridMaker.createTable(200.5, 100, 10, 20).setX(200).setY(100);
	 * </pre>
	 * 
	 * @param w
	 *            The width of the grid.
	 * @param h
	 *            The height of the grid.
	 * @param columns
	 *            The number of columns of the grid.
	 * @param rows
	 *            The number of rows of the grid.
	 * @return The new table grid.
	 */
	static public TableGrid createTable(float w, float h, int columns, int rows) {
		TableGrid grid = new TableGrid(0, 0, w, h, columns, rows, 0, 0);
		return grid;
	}

	/**
	 * Creates a table grid (a grid that is defined by rows and columns). The
	 * elements will be distributed evenly.<br>
	 * This method sets up a basic table grid at location (<b>x, y</b>), with
	 * <b>w</b> width, <b>h</b> height and approx. <b>size</b> elements. The
	 * <b>size</b> is just an approximation because this method tries to create
	 * quadratic grid elements rather then rectangles. But depending on the
	 * width and height of the grid, there might be no solution which will match
	 * the exact given <b>size</b>. You can change these settings after the grid
	 * has been built. Example:<br>
	 * 
	 * <pre>
	 * TableGrid grid = GridMaker.createTable(200, 200, 100, 100, 344);
	 * </pre>
	 * 
	 * @param x
	 *            The x-location of the grid (top-left corner).
	 * @param y
	 *            The y-location of the grid (top-left corner).
	 * @param w
	 *            The width of the grid.
	 * @param h
	 *            The height of the grid.
	 * @param size
	 *            The number of elements for this grid (>= 1).
	 * 
	 * @return The new table grid.
	 */
	static public TableGrid createTable(float x, float y, float w, float h, int size) {

		boolean wh = w > h;

		float aspectRatio = wh ? w / h : h / w;

		int numX = 0;
		int numY = 0;

		if (w == h) {
			numX = numY = (int) (Math.pow(size * aspectRatio, 0.5));
		} else if (wh) {
			numX = (int) (Math.pow(size * aspectRatio, 0.5));
			numY = (int) (size / (float) numX);
		} else {
			numY = (int) (Math.pow(size * aspectRatio, 0.5));
			numX = (int) (size / (float) numY);
		}

		if (numX < 1) {
			numX = 1;
		}

		if (numY < 1) {
			numY = 1;
		}

		TableGrid grid = new TableGrid(x, y, w, h, numX, numY, 0, 0);
		return grid;
	}

	/**
	 * Creates a table grid (a grid that is defined by rows and columns). The
	 * elements will be distributed evenly.<br>
	 * This method sets up a basic table grid at location (<b>x, y</b>), with
	 * <b>w</b> width, <b>h</b> and <b>columns * rows</b> elements. You can
	 * change these settings after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * TableGrid grid = GridMaker.createTable(200, 100, 200.5, 100, 10, 20);
	 * </pre>
	 * 
	 * @param x
	 *            The x-location of the grid (top-left corner).
	 * @param y
	 *            The y-location of the grid (top-left corner).
	 * @param w
	 *            The width of the grid.
	 * @param h
	 *            The height of the grid.
	 * @param columns
	 *            The number of columns of the grid.
	 * @param rows
	 *            The number of rows of the grid.
	 * @return The new table grid.
	 */
	static public TableGrid createTable(float x, float y, float w, float h, int columns, int rows) {

		TableGrid grid = new TableGrid(x, y, w, h, columns, rows, 0, 0);
		return grid;
	}

	/*
	 * ------------------------------------------------------------------------
	 * | EquiSpiral |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral with a given start and end radius. The elements will be
	 * distributed visually evenly (approx.). <br>
	 * This method sets up a basic spiral grid at location (0, 0), with 100
	 * radius and one rotation. The elements will have a distance of 30 to each
	 * other. You can change these settings after the grid has been built.
	 * Example:<br>
	 * 
	 * <pre>
	 * EquiSpiralGrid grid = GridMaker.createEquiSpiral().setX(200).setY(200);
	 * </pre>
	 * 
	 * @return The new spiral grid.
	 */
	static public EquiSpiralGrid createEquiSpiral() {

		EquiSpiralGrid grid = new EquiSpiralGrid(0, 0, 0, 100, 30, 1, 0, 1);
		return grid;
	}
	
	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral with a given start and end radius. The elements will be
	 * distributed visually evenly (approx.). <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with
	 * 100 radius and one rotation. The elements will have a distance of 30
	 * to each other. You can change these settings after the grid has been
	 * built. Example:<br>
	 * 
	 * <pre>
	 * EquiSpiralGrid grid = GridMaker.createEquiSpiral(200, 200).setElementDistance(60);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * 
	 * @return The new spiral grid.
	 */
	static public EquiSpiralGrid createEquiSpiral(float x, float y) {

		EquiSpiralGrid grid = new EquiSpiralGrid(x, y, 0, 100f, 30, 1, 0, 1);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral with a given start and end radius. The elements will be
	 * distributed visually evenly (approx.). <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with
	 * <b>radius</b> and one rotation. The elements will have a distance of 30
	 * to each other. You can change these settings after the grid has been
	 * built. Example:<br>
	 * 
	 * <pre>
	 * EquiSpiralGrid grid = GridMaker.createEquiSpiral(200, 200, 100).setElementDistance(60);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param radius
	 *            Sets the radius where the spiral ends. The last element of
	 *            this grid will be positioned on that radius.
	 * 
	 * @return The new spiral grid.
	 */
	static public EquiSpiralGrid createEquiSpiral(float x, float y, float radius) {

		EquiSpiralGrid grid = new EquiSpiralGrid(x, y, 0, radius, 30, 1, 0, 1);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral with a given start and end radius. The elements will be
	 * distributed visually evenly (approx.). <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with
	 * <b>radius</b> and one rotation. The elements will have a distance of
	 * <b>elementDistance</b> to each other. You can change these settings after
	 * the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EquiSpiralGrid grid = GridMaker.createEquiSpiral(200, 200, 100, 60).setRotations(5.5);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param radius
	 *            Sets the radius where the spiral ends. The last element of
	 *            this grid will be positioned on that radius.
	 * @param elementDistance
	 *            The distance of each element to its neighbors (>= 1).
	 * 
	 * @return The new spiral grid.
	 */
	static public EquiSpiralGrid createEquiSpiral(float x, float y, float radius, float elementDistance) {

		EquiSpiralGrid grid = new EquiSpiralGrid(x, y, 0, radius, elementDistance, 1, 0, 1);
		return grid;
	}

	/**
	 * Creates a spiral grid. The elements of this grid will be located on a
	 * spiral with a given start and end radius. The elements will be
	 * distributed visually evenly (approx.). <br>
	 * This method sets up a basic spiral grid at location (<b>x, y</b>), with
	 * <b>radius</b> and <b>rotations</b> (number of rotations). The elements
	 * will have a distance of <b>elementDistance</b> to each other. You can
	 * change these settings after the grid has been built. Example:<br>
	 * 
	 * <pre>
	 * EquiSpiralGrid grid = GridMaker.createEquiSpiral(200, 200, 100, 60, 5.5);
	 * </pre>
	 * 
	 * @param x
	 *            The center-x location of the grid.
	 * @param y
	 *            The center-y location of the grid
	 * @param radius
	 *            Sets the radius where the spiral ends. The last element of
	 *            this grid will be positioned on that radius.
	 * @param elementDistance
	 *            The distance of each element to its neighbors (>= 1).
	 * @param rotations
	 *            Sets the number of rotations the spiral should make.
	 * @return The new spiral grid.
	 */
	static public EquiSpiralGrid createEquiSpiral(float x, float y, float radius, float elementDistance,
			float rotations) {

		EquiSpiralGrid grid = new EquiSpiralGrid(x, y, 0, radius, elementDistance, rotations, 0, 1);
		return grid;
	}

}
