package edu.uprm.walsaip.vte.core.model;

import static edu.uprm.walsaip.vte.core.datatypes.Angle.PITCH;
import static edu.uprm.walsaip.vte.core.datatypes.Angle.ROLL;
import static edu.uprm.walsaip.vte.core.datatypes.Angle.YAW;
import static edu.uprm.walsaip.vte.core.datatypes.Vector.X;
import static edu.uprm.walsaip.vte.core.datatypes.Vector.Y;
import static edu.uprm.walsaip.vte.core.datatypes.Vector.Z;
import static java.lang.System.out;

import java.io.File;
import java.nio.ByteBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;

import com.sun.opengl.util.BufferUtil;

import edu.uprm.walsaip.vte.core.ModuleInfo;
import edu.uprm.walsaip.vte.core.camera.Camera;
import edu.uprm.walsaip.vte.core.camera.CameraFactory;
import edu.uprm.walsaip.vte.core.datatypes.BoundingBox;
import edu.uprm.walsaip.vte.core.datatypes.Position;
import edu.uprm.walsaip.vte.core.datatypes.Vertex;
import edu.uprm.walsaip.vte.core.datatypes.terrain.ElevationData;
import edu.uprm.walsaip.vte.core.datatypes.terrain.VTEInfo;
import edu.uprm.walsaip.vte.core.datatypes.terrain.TextureData;
import edu.uprm.walsaip.vte.core.exceptions.FilenameNullException;
import edu.uprm.walsaip.vte.core.exceptions.InvalidFileException;
import edu.uprm.walsaip.vte.core.loader.VTEFileLoader;
import edu.uprm.walsaip.vte.core.loader.image.ImageFileLoader;
import edu.uprm.walsaip.vte.core.loader.image.ImageFileLoaderFactory;
import edu.uprm.walsaip.vte.core.loader.terrain.TerrainFileLoader;
import edu.uprm.walsaip.vte.core.loader.terrain.TerrainFileLoaderFactory;
import edu.uprm.walsaip.vte.core.messagebus.Message;
import edu.uprm.walsaip.vte.core.messagebus.MessageBus;
import edu.uprm.walsaip.vte.core.messagebus.MessageTimer;
import edu.uprm.walsaip.vte.core.model.lod.GeoMMLandscape;
import edu.uprm.walsaip.vte.core.model.lod.LODLandscape;
import edu.uprm.walsaip.vte.core.renderer.GeoMipMapRenderer;
import edu.uprm.walsaip.vte.core.renderer.Renderer;
import edu.uprm.walsaip.vte.core.renderer.RendererFactory;
import edu.uprm.walsaip.vte.core.util.GLUtils;


public class GeoMipMapTerrainModel implements TerrainModel {
	public static final String ID = GeoMipMapTerrainModel.class.getSimpleName();


	public static final ModuleInfo<GeoMipMapTerrainModel> MODULE_INFO = new ModuleInfo<GeoMipMapTerrainModel>() {
		protected void setInfo() {
			name = GeoMipMapTerrainModel.class.getSimpleName();
			description = "A Level of Detail Terrain TerrainModel Implementation.";
			moduleClass = GeoMipMapTerrainModel.class;
			supported = true;
		}
	};
		
     // For perspective calculation.
	// For perspective calculation.
	private final float nearClip = 1;
	private boolean loadTexture = true;
	private int blockSize = 64;

	//private int polygonType = GL.GL_FILL;//GL.GL_LINE;


	// A reference to our landscape rendering algorithm.
	private LODLandscape gLand;

	// Camera field of view in degrees.
	private float gFovX = 90.0f;

	private boolean doInit = false;
	//private HeightMap heightMap;
	
	private MessageTimer messageTimer;
	private MessageTimer.Action actionRotateRight;

	private MessageTimer.Action actionRotateLeft;
	private static float deltaAngle = 2.0f;
	private int movementDelay = 50;
//	private static float movementOffset = 100.0f;
//	private int rotationDelay = 10;
//	private static float zoomFactor = 10f;


	protected TerrainFileLoader terrainLoader;


	protected ImageFileLoader imageLoader;


	protected ElevationData terrainData = null;

	protected VTEInfo terrainInfo = null;
	protected TextureData imageData = null;


	protected BoundingBox box;


	protected float gridSpacingX = 1;


	protected float gridSpacingY = 1;


	protected String filename = null;


	protected Position position;


	protected Vertex center;


	protected int gTextureID = 1;


	protected Camera camera;


	protected Renderer renderer;


	protected int windowRes = 0;


	
	public GeoMipMapTerrainModel() {
		super();
		MessageBus.addListener(this,TerrainModel.ID);
		this.camera = CameraFactory.getCurrentCamera();
		this.position = new Position();
		this.center = new Vertex();
		//this.position.rotateAroundAxisY(180);
		this.position.getAngle().setPitch(90);
		MessageBus.addListener(this,Renderer.ID);
		messageTimer = new MessageTimer();
		initEventActions();
		//this.position.getAngle().setYaw(180);
		//sthis.position.getPosition().incrementZ(-)
	}


	

	public void init(String filename) throws FilenameNullException, InvalidFileException {
		
		File file = new File(filename);
		terrainInfo = VTEFileLoader.read(file);
		blockSize = terrainInfo.getBlockSize();
		//Constants.setBlockSize(blockSize);
		
		
		this.filename = filename;
		
		imageData = loadTextureData(terrainInfo);
		terrainData = loadElevationData(terrainInfo);
		centerModel();
		int terrainHeight = terrainData.getHeight();
		int terrainWidth = terrainData.getWidth();
		gridSpacingX = terrainData.getGridSpacingX();
		gridSpacingY = terrainData.getGridSpacingY();
		int cam_offset = ( terrainHeight > terrainWidth ? terrainHeight: terrainWidth); 
		Position p = new Position();
		
		
		p.getPosition().setZ((terrainData.getElevation())+(terrainData.getGridSpacingX()*0.5f*cam_offset));
	
	   MessageBus.sendMessage(new Message<Position>(Camera.ID,Camera.SET_POSITION,p));
	   MessageBus.sendMessage(new Message<Float>(Camera.ID,Camera.SET_MAX_MODEL_ELEVATION,terrainData.getElevation()));
	}
	
	private  void initRenderData(GL gl) {
		
		if (gLand == null) {
		out.printf("Generating GeoMipmaps...");
		gLand = new GeoMMLandscape(renderer,terrainData, blockSize, gridSpacingX, gridSpacingY,	nearClip, (float) (nearClip * Math.tan(gFovX / 2.0* Math.PI / 180.0)), windowRes, 1);
		out.println("done");
		}
		
		if (loadTexture)
			setupTextures(gl);

		doInit = false;
	}
	
	
	public void init(GL gl) {

		if (renderer == null) {
			renderer = RendererFactory.getRendererByName(GeoMipMapRenderer.ID);
		}
		renderer.init(gl);	
		
	}

	public int render(GL gl) {
		
		if (doInit){
			if (renderer == null)
				init(gl);	
			
			initRenderData(gl);	
		}
		
		float cameraPosition [] = this.camera.getPosition().getPosition().getAsArray();
	
		float clipAngle = camera.getPosition().getAngle().getYaw();
		
		gl.glPushMatrix();
		translateAndRotate(gl, this.position);
		float fov = GLUtils.range360(gFovX + (360 - this.position.getAngle().getPitch()));
		float viewPosition[] = Vertex.subtract(cameraPosition, this.position.getPosition().getAsArray());
		int trianglesRendered = 0;
		if(gLand != null) {
			gLand.update(fov, viewPosition, -clipAngle);
			gLand.render(gl);
			trianglesRendered= gLand.numTrianglesRendered();
		}
		gl.glPopMatrix();
		return trianglesRendered;
	}


	

	
	
	protected ElevationData loadElevationData(VTEInfo info) {
		File file = new File(info.getFilename());
		String dataPath = file.getParent();
		String filename = dataPath+File.separator+terrainInfo.getDemSourceFilename();
		
		
		
		if (terrainLoader == null) 
			terrainLoader = TerrainFileLoaderFactory.getLoader(filename);
		ElevationData  data = new ElevationData(info);
		terrainLoader.read(data,info,new File(filename));


		return data;
	}

	protected TextureData loadTextureData(VTEInfo info)  {
		File file = new File(info.getFilename());
		String dataPath = file.getParent();
		
		String filename = dataPath+File.separator+terrainInfo.getTextureSourceFilename();
		if (imageLoader == null)  {
			imageLoader = ImageFileLoaderFactory.getLoader(filename);
		}
		TextureData data = new TextureData(info);
		imageLoader.read(data,info,new File(filename));
		
		return data;
	}


	protected void translateAndRotate(GL gl,Position p) {
	
		float position[] = p.getPosition().getAsArray();
		float angle[] = p.getAngle().getAsArray();

		gl.glRotatef(angle[PITCH], 1.0f, 0.0f, 0.0f);
		gl.glRotatef(angle[YAW], 0.0f, 1.0f, 0.0f);
		gl.glRotatef(angle[ROLL], 0.0f, 0.0f, 1.0f);

		gl.glTranslatef(position[X], position[Y], position[Z]);
	}
	
	protected void setupTextures(GL gl) {
//		File file = new File(terrainInfo.getFilename());
//		String dataPath = file.getParent();
//		
//		String filename = dataPath+File.separator+terrainInfo.getTextureBlocks().get(0);
//		imageLoader.read(imageData,terrainInfo,new File(filename));
		
		//GLU glu = new GLU();
	
		//gl.glEnable(GL.GL_LIGHTING);
		gTextureID = renderer.getNextTextureId();
		
	
		
		gl.glBindTexture(GL.GL_TEXTURE_2D, gTextureID);
		// Allocate memory for the texture map.
		int width = imageData.getWidth();
		int height = imageData.getHeight();
		

		//Display.NewMsg("Generating Mipmaps...");
		out.printf("Generating Mipmaps...");
		
		ByteBuffer byteBuf = imageData.getByteBuffer();
		byteBuf.rewind();

		gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
	    
	
		//new GLU().gluBuild2DMipmaps(GL.GL_TEXTURE_2D, GL.GL_RGBA, width, height,GL.GL_RGBA,  GL.GL_UNSIGNED_BYTE, byteBuf);

	    gl.glTexImage2D(GL.GL_TEXTURE_2D, 0,GL.GL_RGBA,  width, height,0,GL.GL_RGBA,  GL.GL_UNSIGNED_BYTE, byteBuf);
	    
	    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
		gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,  GL.GL_NEAREST);
		    
//	    gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
//		gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,	GL.GL_LINEAR);
//		//gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,	GL.GL_LINEAR_MIPMAP_LINEAR);
	    
	

	     gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_DECAL);
	    // );
		
		// Use Generated Texture Coordinates
		float s_vector[] = { 1f / (float) (terrainData.getWidth()+1), 0, 0, 0 };
		float t_vector[] = { 0, 0, 1f / (float) (terrainData.getHeight()+1), 0 };

		gl.glTexGeni(GL.GL_S, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR);
		gl.glTexGenfv(GL.GL_S, GL.GL_OBJECT_PLANE, s_vector, 0);

		gl.glTexGeni(GL.GL_T, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR);
		gl.glTexGenfv(GL.GL_T, GL.GL_OBJECT_PLANE, t_vector, 0);

		gl.glEnable(GL.GL_TEXTURE_2D);
		gl.glEnable(GL.GL_TEXTURE_GEN_S);
		gl.glEnable(GL.GL_TEXTURE_GEN_T);
		//Display.println("done");
		//texturesEnabled = true;
		imageData = null;
		loadTexture = false;
		out.println("done");
		
	}

	
	  
	
	
//	protected int genTexture(GL gl) {
//		final int[] tmp = new int[1];
//		gl.glGenTextures(1, tmp, 0);
//		return tmp[0];
//	}

	protected BoundingBox getMaxBoundingBox() {
	
		return terrainData.getBoundingBox();
	}

	protected void centerModel() {
		this.box = getMaxBoundingBox();
		
		position.getPosition().setX(-terrainData.getWidth()*0.5f*terrainData.getGridSpacingX());
		position.getPosition().setZ(-terrainData.getHeight()*0.5f*terrainData.getGridSpacingY());
		
	}

	
	


private void initEventActions() {
		
	
		actionRotateLeft = messageTimer.new Action() {
					@Override
					public void exec() {
						position.rotateAroundAxisY(-deltaAngle);
					}
				};
		actionRotateRight = messageTimer.new Action() {
					@Override
					public void exec() {
						position.rotateAroundAxisY(deltaAngle);
					}
				};
		
		
	
		
	}
public void processEvent(Message<?> m) {
	
	String name = m.getName();
	Object value = m.getValue();
	String type = m.getType();
		if (type.equals(Message.MSG_START)) { 
		if (name.equals(ROTATE_Y_MINUS)) 
			messageTimer.startEvent(actionRotateRight, movementDelay);	
		else if (name.equals(ROTATE_Y_PLUS)) 
			messageTimer.startEvent(actionRotateLeft, movementDelay);	

		
	}		
	else if (type.equals(Message.MSG_STOP))
		messageTimer.stopEvent();
	else if (type.equals(Message.MSG_INSTANT) && !messageTimer.isTimerStarted()) {
		
		
		//	out.println(e.getTarget() + " " +  e.getAction());
		
		if (name.equals(INIT)) {
		//	initDone = false;
			if (value != null)
				try {
					init(String.class.cast(value));
				} catch (FilenameNullException e) {
					e.printStackTrace();
				} catch (InvalidFileException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			
		//	MessageBus.sendMessage(new Message<String>(View.ID,Module.INIT,null));
			if (windowRes != 0)
				doInit = true;
		}
		else if (name.equals(WINDOW_HEIGHT)) {
			windowRes = Integer.class.cast(value);
			if (filename != null )
				doInit = true;
		}
		 else if (name.equals(ROTATE_AROUND_X)) {
			float pitch = position.getAngle().getPitch();
			pitch = 270.0f+(Float.class.cast(value)/4.0f);
			position.getAngle().setPitch(pitch);
		} else if (name.equals(ROTATE_X_PLUS)) {
		
			float pitch = position.getAngle().getPitch();
			pitch = GLUtils.range360(pitch - 0.7f);
			//System.out.println("pitch360: " + pitch);
			if (pitch < 270f && pitch > 0)
				pitch = 270f;
			//System.out.println("pitchUp: " + pitch);
			position.getAngle().setPitch(pitch);

		} else if (name.equals(ROTATE_Y_PLUS)) {
			position.getAngle().incrementYaw(0.7f);
		} else if (name.equals(ROTATE_Z_PLUS)) {
			position.getAngle().incrementRoll(0.7f);
		}  else if (name.equals(ROTATE_X_MINUS)) {
			float pitch = position.getAngle().getPitch();
			pitch = pitch + 0.7f;
			if (pitch < 270)
				pitch = 0f;

			if (pitch > 360f)
				pitch = 360f;
			position.getAngle().setPitch(pitch);

		} else if (name.equals(ROTATE_Y_MINUS)) {
			position.getAngle().incrementYaw(-0.7f);
		} else if (name.equals(ROTATE_Z_MINUS)) {
			position.getAngle().incrementRoll(-0.7f);
		} else if (name.equals(MOVE_X_PLUS)) {
			position.getPosition().incrementX(-5.0f);
		} else if (name.equals(MOVE_X_MINUS)) {
			position.getPosition().incrementX(5.0f);
		} else if (name.equals(MOVE_Y_PLUS)) {
			position.getPosition().incrementY(-5.0f);
		} else if (name.equals(MOVE_Y_MINUS)) {
			position.getPosition().incrementY(5.0f);
		} else if (name.equals(MOVE_Z_PLUS)) {
			position.getPosition().incrementZ(-5.0f);
		} else if (name.equals(MOVE_Z_MINUS)) {
			position.getPosition().incrementZ(5.0f);
		}
		else if (name.equals(SET_PITCH)) {
			float pitch = Float.class.cast(value);
			
			//System.out.println(pitch);
			position.getAngle().setPitch(pitch);
		}
		else if (name.equals(LOAD)) {
			loadTexture = true;
			doInit = true;
		}
		
	} 
	
	
	
}
}
