package object;

import javax.media.opengl.GL;

public class AutoHeightMap {
	
	protected double[][] map;
	
	protected double highestPoint;
	protected double lowestPoint;
	
	protected double xPos, yPos, zPos;
	
	public AutoHeightMap() {
		
	}
	
	public void generate(int size, int diamondSquareIterations, int smoothness) {
		
		map = new double[size][size];
		
		highestPoint = Double.MIN_VALUE;
		lowestPoint = Double.MAX_VALUE;

		xPos = 0;
		yPos = 0;
		zPos = 0;
		
		for(int x = 0; x < size; x++)
			for(int z = 0; z < size; z++)
				map[x][z] = 0;
		
		map[0][0] = Math.random() * 2 - 1;
		map[size - 1][0] = Math.random() * 2 - 1;
		map[0][size - 1] = Math.random() * 2 - 1;
		map[size - 1][size - 1] = Math.random() * 2 - 1;
		
		diamondSquare(0, size - 1, 0, size - 1, 0, diamondSquareIterations);
		
		for(int smooth = 0; smooth < smoothness; smooth++)
		
			for(int x = 0; x < size; x++)
				for(int z = 0; z < size; z++)
					
					smooth(x, z);
		
		for(int x = 0; x < size; x++)
			for(int z = 0; z < size; z++) {
				
				if(map[x][z] > highestPoint)
					highestPoint = map[x][z];
				
				if(map[x][z] < lowestPoint)
					lowestPoint = map[x][z];
			}
	}
	
	private void diamondSquare(int xi, int xf, int zi, int zf,
			int iteration, int iterationLimit) {
		
		if(map[xi + (xf - xi) / 2][zi] == 0)
			map[xi + (xf - xi) / 2][zi] = (map[xi][zi] + map[xf][zi]) / 2 + getRandomNumber(1) / (iteration + 1);
		
		if(map[xi + (xf - xi) / 2][zf] == 0)
			map[xi + (xf - xi) / 2][zf] = (map[xi][zf] + map[xf][zf]) / 2 + getRandomNumber(1) / (iteration + 1);
		
		if(map[xi][zi + (zf - zi) / 2] == 0)
			map[xi][zi + (zf - zi) / 2] = (map[xi][zi] + map[xi][zf]) / 2 + getRandomNumber(1) / (iteration + 1);
		
		if(map[xf][zi + (zf - zi) / 2] == 0)
			map[xf][zi + (zf - zi) / 2] = (map[xf][zi] + map[xf][zf]) / 2 + getRandomNumber(1) / (iteration + 1);
		
		if(map[xi + (xf - xi) / 2][zi + (zf - zi) / 2] == 0)
			map[xi + (xf - xi) / 2][zi + (zf - zi) / 2] =
			(map[xi][zi] + map[xf][zi] + map[xi][zf] + map[xf][zf]) / 4 + getRandomNumber(1) / (iteration + 1);
		
		if(iteration < iterationLimit) {
			diamondSquare(xi, xi + (xf - xi) / 2, zi, zi + (zf - zi) / 2, iteration + 1, iterationLimit);
			diamondSquare(xi + (xf - xi) / 2, xf, zi, zi + (zf - zi) / 2, iteration + 1, iterationLimit);
			diamondSquare(xi, xi + (xf - xi) / 2, zi + (zf - zi) / 2, zf, iteration + 1, iterationLimit);
			diamondSquare(xi + (xf - xi) / 2, xf, zi + (zf - zi) / 2, zf, iteration + 1, iterationLimit);
		}
	}
	
	private void smooth(int x, int z) {
		
		double area = map[x][z];
		double divide = 1;
		
		if(x > 0) {
			area += map[x - 1][z];
			divide++;
		}
		
		if(x < map.length - 1) {
			area += map[x + 1][z];
			divide++;
		}
		
		if(z > 0) {
			area += map[x][z - 1];
			divide++;
		}
		
		if(z < map[0].length - 1) {
			area += map[x][z + 1];
			divide++;
		}
		
		map[x][z] = area / divide;
	}
	
	private double getRandomNumber(double range) {
		return Math.random() * range * 2 - range;
	}
	
	public void setSize(int width, int height) {
		map = new double[width][height];
	}
	
	public double[][] getMapData() {
		return map;
	}
	
	public void setValueAt(int x, int z, double height) {
		
		if(map[x][z] == highestPoint)
			highestPoint = height;
		
		if(map[x][z] == lowestPoint)
			lowestPoint = height;
		
		map[x][z] = height;
	}
	
	public double getValueAt(int x, int z) {
		return map[x][z];
	}
	
	public double getLowestPoint() {
		return lowestPoint;
	}
	
	public double getHighestPoint() {
		return highestPoint;
	}
	
	public void setPosition(double xPos, double yPos, double zPos) {
		this.xPos = xPos;
		this.yPos = yPos;
		this.zPos = zPos;
	}
	
	public void render(GL gl, double width, double length, double height) {
		
		width /= map.length;
		length /= map[0].length;
		height /= highestPoint - lowestPoint;
		
		for(int x = 0; x < map.length - 2; x++)
			for(int z = 0; z < map.length - 2; z++) {
				
				gl.glBegin(GL.GL_QUADS);
				
				setColor(gl, x, z);
				
				gl.glVertex3d(
						(x * width) - ((width * map.length) / 2) + xPos,
						map[x][z] * height + yPos,
						(z * length) - ((length * map[0].length) / 2) + zPos
						);
				
				gl.glVertex3d(
						(x * width) - ((width * map.length) / 2) + xPos,
						map[x][z + 1] * height + yPos,
						((z + 1) * length) - ((length * map[0].length) / 2) + zPos
						);
				
				gl.glVertex3d(
						((x + 1) * width) - ((width * map.length) / 2) + xPos,
						map[x + 1][z + 1] * height + yPos,
						((z + 1) * length) - ((length * map[0].length) / 2) + zPos
						);
				
				gl.glVertex3d(
						((x + 1) * width) - ((width * map.length) / 2) + xPos,
						map[x + 1][z] * height + yPos,
						(z * length) - ((length * map[0].length) / 2) + zPos
						);
				
				gl.glEnd();
			}
	}
	
	protected void setColor(GL gl, int x, int y) {
		
		double height = (map[x][y] + map[x + 1][y] + map[x][y + 1] + map[x + 1][y + 1]) / 4;
		
		gl.glColor3f(
				(float) ((height - lowestPoint) * (1 / (highestPoint - lowestPoint))),
				(float) ((height - lowestPoint) * (1 / (highestPoint - lowestPoint))),
				(float) ((height - lowestPoint) * (1 / (highestPoint - lowestPoint)))
				);
	}
}