package water;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.lwjgl.util.vector.Vector2f;

import openglObjects.Vao;
import vertexDataStoring.DataStoring;
import vertexDataStoring.VaoLoader;

/**
 * 
 * Generates a simple flat grid mesh which will be used as the water. Very
 * similar to how the "split terrain" was generated in tutorial 55. Each
 * triangle of the mesh has its own 3 vertices so that no vertices are shared
 * between triangles. This allows for the flat-shaded look as the
 * normals/lighting values aren't interpolated across multiple triangles.
 * 
 * @author Karl
 *
 */
public class WaterGenerator {

	private static final int VERTICES_PER_SQUARE = 3 * 2;// 2 triangles
	private static final int VERTEX_SIZE_BYTES = 8 + 4;// x,z position +
														// indicator

	/**
	 * Generates a water mesh of a given size. First the total number of
	 * vertices in the mesh is calculated. Then all the vertex data for the mesh
	 * is generated and stored in a byte[]. A VAO is then created, and the
	 * vertex data is stored in it.
	 * 
	 * @param gridCount
	 *            - The number of grid squares along each edge of the mesh.
	 * @param height
	 *            - The height of the water mesh in the world.
	 * @return The newly constructed water mesh.
	 */
	public static WaterTile generate(int gridCount, float height) {
		int totalVertexCount = gridCount * gridCount * VERTICES_PER_SQUARE;
		byte[] waterMeshData = createMeshData(gridCount, totalVertexCount);
		Vao vao = VaoLoader.createWaterVao(waterMeshData);
		return new WaterTile(vao, totalVertexCount, height);
	}

	/**
	 * Generates all the vertex data for the water mesh. First a byte buffer of
	 * the correct size is initialized, and this will hold all the vertex data
	 * for the entire mesh. It then iterates through all the grid squares in the
	 * mesh and for each one it stores the vertex data for the two triangles in
	 * that square. Therefore, the data for 6 vertices is stored per grid
	 * square.
	 * 
	 * @param gridCount
	 *            - The number of grid squares along one edge of the mesh.
	 * @param totalVertexCount
	 *            - The total number of vertices that will be in the mesh.
	 * @return The vertex data (stored as bytes) for the entire mesh.
	 */
	private static byte[] createMeshData(int gridCount, int totalVertexCount) {
		int byteSize = VERTEX_SIZE_BYTES * totalVertexCount;
		ByteBuffer buffer = ByteBuffer.allocate(byteSize).order(ByteOrder.nativeOrder());
		for (int row = 0; row < gridCount; row++) {
			for (int col = 0; col < gridCount; col++) {
				storeGridSquare(col, row, buffer);
			}
		}
		return buffer.array();
	}

	/**
	 * Generates all the vertex data for a grid square. The corner positions of
	 * the grid square are calculated and then the data for the two triangles in
	 * this square is stored. Note that the data for the two triangles is stored
	 * separately, meaning that the data for 6 vertices is stored.
	 * 
	 * @param col
	 *            - The column number of this grid square in the grid.
	 * @param row
	 *            - The row number of this grid square in the grid.
	 * @param buffer
	 *            - The buffer where all the vertex data is being collected.
	 */
	private static void storeGridSquare(int col, int row, ByteBuffer buffer) {
		Vector2f[] cornerPos = calculateCornerPositions(col, row);
		storeTriangle(cornerPos, buffer, true);
		storeTriangle(cornerPos, buffer, false);
	}

	/**
	 * Stores the vertex data for a given triangle of the mesh into the
	 * ByteBuffer. First it is determined which 3 of the vertices of the current
	 * grid square make up the triangle. The indexes for a grid square are as
	 * follows:
	 * 
	 * 0 = top left, 1 = bottom left, 2 = top right, 3 = bottom right.
	 * 
	 * This is the order that the corner positions are stored in the "cornerPos"
	 * array.
	 * 
	 * Once it has been determined which 3 vertices make up the triangle, the
	 * vertex data for those 3 vertices is stored in the ByteBuffer. For each
	 * vertex the x,z position is stored, along with the 4 indicator values.
	 * 
	 * @param cornerPos
	 *            - The 4 corner positions for the current grid square, stored
	 *            in the order specified above.
	 * @param buffer
	 *            - The buffer containing all the vertex data for the mesh.
	 * @param left
	 *            - Indicates whether the triangle being stored is the triangle
	 *            on the left or the right of the current grid square.
	 */
	private static void storeTriangle(Vector2f[] cornerPos, ByteBuffer buffer, boolean left) {
		int index0 = left ? 0 : 2;
		int index1 = 1;
		int index2 = left ? 2 : 3;
		DataStoring.packVertexData(cornerPos[index0], getIndicators(index0, cornerPos, index1, index2), buffer);
		DataStoring.packVertexData(cornerPos[index1], getIndicators(index1, cornerPos, index2, index0), buffer);
		DataStoring.packVertexData(cornerPos[index2], getIndicators(index2, cornerPos, index0, index1), buffer);
	}

	/**
	 * Calculates the x,z positions of the 4 corners of a grid square.
	 * 
	 * @param col
	 *            - The column number of the grid square.
	 * @param row
	 *            - The row number of the grid square.
	 * @return An array contain 4 positions. Each 2D position is the x,z
	 *         position of one of the corners. The corners are stored in the
	 *         following order: 0 = top left, 1 = bottom left, 2 = top right, 3
	 *         = bottom right
	 */
	private static Vector2f[] calculateCornerPositions(int col, int row) {
		Vector2f[] vertices = new Vector2f[4];
		vertices[0] = new Vector2f(col, row);
		vertices[1] = new Vector2f(col, row + 1);
		vertices[2] = new Vector2f(col + 1, row);
		vertices[3] = new Vector2f(col + 1, row + 1);
		return vertices;
	}

	/**
	 * Gets the 4 indicator values for a certain vertex. This is done by
	 * calculating the vector from the current vertex to each of the other two
	 * vertices in the current triangle.
	 * 
	 * The 3 vertex positions are taken from the "vertexPositions" array, and
	 * then the offset vectors are calculated by subtracting the current vertex
	 * position from the other vertex position.
	 * 
	 * The offsets are then stored in an array as bytes (not converted to bytes,
	 * but simply cast to bytes) and returned. The size of each grid square must
	 * be an integer value for this to work, otherwise the offsets wouldn't be
	 * able to be represented correctly as bytes.
	 * 
	 * @param currentVertex
	 *            - The index of the current vertex in the current grid square
	 *            (A number between 0 and 3).
	 * @param vertexPositions
	 *            - The 4 corner positions of the current grid square, stored in
	 *            the following order: 0 = top left, 1 = bottom left, 2 = top
	 *            right, 3 = bottom right
	 * @param vertex1
	 *            - The index of one of the other vertices in the triangle
	 *            (number between 0 and 3).
	 * @param vertex2
	 *            - The index of the other vertex in the triangle (number
	 *            between 0 and 3).
	 * @return
	 */
	private static byte[] getIndicators(int currentVertex, Vector2f[] vertexPositions, int vertex1, int vertex2) {
		Vector2f currentVertexPos = vertexPositions[currentVertex];
		Vector2f vertex1Pos = vertexPositions[vertex1];
		Vector2f vertex2Pos = vertexPositions[vertex2];
		Vector2f offset1 = Vector2f.sub(vertex1Pos, currentVertexPos, null);
		Vector2f offset2 = Vector2f.sub(vertex2Pos, currentVertexPos, null);
		return new byte[] { (byte) offset1.x, (byte) offset1.y, (byte) offset2.x, (byte) offset2.y };
	}

}