package simogl.level;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.media.opengl.GL;

import simogl.geometry.Face;
import simogl.geometry.MeshDL;
import simogl.geometry.Point3D;
import simogl.geometry.Triangle;

/**
 * This class represents a world's terrain, that is generated from 
 * a height map file.
 * @author Frank Bruns
 *
 */
public class Terrain extends MeshDL
{	
	private int mapSize = 0;
	private int terrainResolutionFactor = 0;
	private byte heightMapData[] = null;	
	
	
	/**
	 * Creates a new height map of the specified file with the specified
	 * height map rendering resolution factor.
	 * The smaller the value for the resolution factor, the more 
	 * precisely the height map image is being processed, resulting in 
	 * a terrain with more triangles, i.e. a smoother look.
	 * <br>
	 * <b>Note: </b>Please only use image files with a power of 2 resolution
	 * and confirm that image height and width are both equal.
	 * <br>
	 * Furthermore, please avoid very small values like 1,2,3 or 4 as resFactor.
	 * They have a remarkably negative effect on performance and can cause an
	 * out of memory exception, too. A good value to start with would
	 * be 16 or 32.
	 * @param gl
	 * @param file file that contains the height map data
	 * @param mapSize pixel width/height of the height map image
	 * @param resFactor height map rendering resolution factor
	 */
	public Terrain(GL gl, File file, int mapSize, int resFactor)
	{
		super(gl);
		this.mapSize = mapSize;
		this.terrainResolutionFactor = resFactor;
		this.heightMapData = this.loadHeightMapFile(file, this.mapSize*this.mapSize);
		
		this.createTerrain();
		this.createDisplayList(gl);
	}
		
	/**
	 * This helper method loads an image file that is available in greyscale format.
	 * This can be files like .bmp or .raw or other files in greyscale mode.
	 * @param file height map file
	 * @param size calculated as pixel height * pixel width of the image
	 * @return byte array with the greyscale data
	 */
	private byte[] loadHeightMapFile(File file, int size)
	{
		byte[] map = new byte[size];
		FileInputStream input = null;

		try
		{
			input = new FileInputStream(file);
		}
		catch (IOException e)
		{
			System.out.println("Cannot find height map file!");
		}
		try
		{			
			input.read(map, 0, size);
			input.close();
		}
		catch (IOException e)
		{
			System.out.println("Cannot read the height map file!");
		}
		//for (int i = 0; i < map.length; i++) map[i] &= 0xFF;
		
		return map;
	}
	
	/**
	 * Returns the height from a height map index
	 * @param mapData byte-array with map data
	 * @param xCounter xCounter from createTerrain()-method
	 * @param yCounter yCounter from createTerrain()-method
	 * @return height of a height map index
	 */
	private int getHeight(byte[] mapData, int xCounter, int yCounter)
	{ 
		int x = xCounter % mapSize;
		int y = yCounter % mapSize;
		return (mapData[x + (y * mapSize)] & 0xFF); 
	}
	
	/**
	 * Creates the points, triangles and faces of the terrain
	 */
	private void createTerrain()
	{
		Point3D p1 = null;
		Point3D p2 = null;
		Point3D p3 = null;
		Triangle trig1 = null;
		Triangle trig2 = null;
		Face face = null;
		
		float u = 1f/(this.mapSize);
		float v = 1f/(this.mapSize);
		
		// counters for the image's x and y direction in the byte[]-array 
		int xCounter = 0, yCounter = 0;
		
		// the x,y,z component of each point
		int x, y, z;
	

		for (xCounter = 0; xCounter < (mapSize - terrainResolutionFactor); xCounter += terrainResolutionFactor)
		{
			for (yCounter = 0; yCounter < (mapSize - terrainResolutionFactor); yCounter += terrainResolutionFactor)
			{
				
				// data for triangle 1
			
				// bottom left vertex
				x = xCounter+(int)this.getPositionX();
				y = this.getHeight(this.heightMapData, xCounter, yCounter)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ();
				p1 = new Point3D(x,y,-z);
				// bottom right vertex
				x = xCounter+(int)this.getPositionX() + terrainResolutionFactor;
				y = this.getHeight(this.heightMapData, xCounter + terrainResolutionFactor, yCounter)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ();
				p2 = new Point3D(x,y,-z);
				// top right vertex
				x = xCounter+(int)this.getPositionX() + terrainResolutionFactor;
				y = this.getHeight(this.heightMapData, xCounter + terrainResolutionFactor, yCounter + terrainResolutionFactor)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ() + terrainResolutionFactor;
				p3 = new Point3D(x,y,-z);				
				trig1 = new Triangle(p1,p2,p3);
				
				
				// data for triangle 2
				
				// top right vertex
				x = xCounter+(int)this.getPositionX() + terrainResolutionFactor;
				y = this.getHeight(this.heightMapData, xCounter + terrainResolutionFactor, yCounter + terrainResolutionFactor)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ() + terrainResolutionFactor;
				p1 = new Point3D(x,y,-z);				
				// top left vertex
				x = xCounter+(int)this.getPositionX();;
				y = this.getHeight(this.heightMapData, xCounter, yCounter + terrainResolutionFactor)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ() + terrainResolutionFactor;
				p2 = new Point3D(x,y,-z);				
				// bottom left vertex
				x = xCounter+(int)this.getPositionX();;
				y = this.getHeight(this.heightMapData, xCounter, yCounter)+(int)this.getPositionY();
				z = yCounter+(int)this.getPositionZ();
				p3 = new Point3D(x,y,-z);				
				trig2 = new Triangle(p1,p2,p3);
				
				// create face from triangles and uv-map the texture
				// over the whole terrain
				face = new Face(trig1,trig2);

				face.getTriangle1().setTexCoords(
						u * face.getTriangle1().getVertex1().getX(),
						v * -face.getTriangle1().getVertex1().getZ(),
						u * face.getTriangle1().getVertex2().getX(),
						v * -face.getTriangle1().getVertex2().getZ(),
						u * face.getTriangle1().getVertex3().getX(),
						v * -face.getTriangle1().getVertex3().getZ());
				
				face.getTriangle2().setTexCoords(
						u * face.getTriangle2().getVertex1().getX(),
						v * -face.getTriangle2().getVertex1().getZ(),
						u * face.getTriangle2().getVertex2().getX(),
						v * -face.getTriangle2().getVertex2().getZ(),
						u * face.getTriangle2().getVertex3().getX(),
						v * -face.getTriangle2().getVertex3().getZ());
				
				this.addFace(face);							
			}
		}			
			System.out.println("Terrain-Faces: "+this.getFaces().size());
	}
	
	/**
	 * Returns the resolution factor of the terrain. This factor is
	 * equal to the lenght of triangle edges in the terrain mesh.
	 * @return terrain resolution factor
	 */
	public int getResolution()
	{
		return this.terrainResolutionFactor;
	}
}
