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

import static edu.uprm.walsaip.vte.core.util.BufferUtils.SIZEOF_FLOAT;
import static edu.uprm.walsaip.vte.core.util.BufferUtils.SIZEOF_INT;
import static java.lang.System.err;
import static java.lang.System.out;

import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.GLContext;

import edu.uprm.walsaip.vte.core.ModuleInfo;
import edu.uprm.walsaip.vte.core.datatypes.terrain.TerrainData;
import edu.uprm.walsaip.vte.core.messagebus.Message;
import edu.uprm.walsaip.vte.core.messagebus.MessageBus;

public class VBORenderer implements Renderer {

	public static final String ID = VBORenderer.class.getSimpleName();
	public static final String Type = Renderer.ID;
	public static final ModuleInfo<VBORenderer> MODULE_INFO = new ModuleInfo<VBORenderer>() {
		
		protected void setInfo() {
			name = VBORenderer.ID;
			description = "Vertex Buffer Object Renderer";
			moduleClass = VBORenderer.class;
		
		}
		
		public boolean isSupported() {
			GL gl = GLContext.getCurrent().getGL();
			return gl.isFunctionAvailable("glBindBuffer");  
		}
	};
	
	
	private VertexBufferData vboData;
	private boolean enabledArray[] = { false, false, false, false };
	private int primitiveType = GL.GL_TRIANGLES;//GL.GL_QUADS;
	private int polygonType = GL.GL_FILL;
	private boolean reinit = false;
	boolean colorMode = false;

	protected VBORenderer() {
		super();
		MessageBus.addListener(this,Renderer.ID);
	}

	public void init(GL gl) {
		
		out.printf("GL Driver: Vendor[ %s ] Renderer [ %s]",gl.glGetString(GL.GL_VENDOR),gl.glGetString(GL.GL_RENDERER));

		gl.setSwapInterval(1);
		gl.glEnable(GL.GL_CULL_FACE);
		gl.glCullFace(GL.GL_BACK);
		gl.glFrontFace(GL.GL_CW);

		gl.glEnable(GL.GL_LIGHTING);
		gl.glLightModeli(GL.GL_LIGHT_MODEL_LOCAL_VIEWER, GL.GL_TRUE);
		float globalAmbientLight[] = { 0.4f, 0.4f, 0.4f, 1.0f };
		gl.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, globalAmbientLight, 0);
		gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		gl.glClearDepth(1.0);
		gl.glDepthFunc(GL.GL_LEQUAL);
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glShadeModel(GL.GL_SMOOTH);
		gl.glEnable(GL.GL_POLYGON_SMOOTH);
		gl.glEnable(GL.GL_COLOR_MATERIAL);
		gl.glColorMaterial(GL.GL_FRONT, GL.GL_AMBIENT);
	
		gl.glEnable(GL.GL_NORMALIZE);
		gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_FASTEST);
		gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_FASTEST);
		gl.glHint(GL.GL_POINT_SMOOTH_HINT, GL.GL_FASTEST);
		gl.glHint(GL.GL_POLYGON_SMOOTH_HINT, GL.GL_FASTEST);

		gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
		out.println(VBORenderer.ID+" Initialization: completed");
	}
	
	public void init(GL gl,  TerrainData terrainData,  TerrainData imageData) {		
		
	
		init(gl);

		if (vboData == null || reinit) {
			vboData = new VertexBufferData(terrainData,imageData,primitiveType);
			reinit = false;
		}
		FloatBuffer vertexBuffer = vboData.getVertexBuffer();
		FloatBuffer colorBuffer = vboData.getColorBuffer();
		FloatBuffer normalBuffer = vboData.getNormalBuffer();
		IntBuffer indexBuffer = vboData.getIndexBuffer();

		IntBuffer idBuffer = vboData.getIdBuffer();
		gl.glGenBuffers(4, idBuffer);

		if (vertexBuffer != null) {
			
			bindVertexBufferObject(gl, GL.GL_ARRAY_BUFFER,
					VertexBufferData.VERTEX_ID, vertexBuffer.remaining(), idBuffer,
					vertexBuffer,"Vertex VBO");
			gl.glEnableClientState(GL.GL_VERTEX_ARRAY);
			gl.glVertexPointer(4, GL.GL_FLOAT, 0, 0);
		}

		if (colorBuffer != null) {
			bindVertexBufferObject(gl, GL.GL_ARRAY_BUFFER,
					VertexBufferData.COLOR_ID, colorBuffer.remaining(), idBuffer,
					colorBuffer,"Color VBO");
			if (colorMode)
				gl.glEnableClientState(GL.GL_COLOR_ARRAY);
			gl.glColorPointer(4, GL.GL_FLOAT, 0, 0);

		}

		if (normalBuffer != null) {
			bindVertexBufferObject(gl, GL.GL_ARRAY_BUFFER,
					VertexBufferData.NORMAL_ID, normalBuffer.remaining(), idBuffer,
					normalBuffer,"Normals VBO");
			gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
			gl.glNormalPointer(GL.GL_FLOAT, 0, 0);
			
		}

		if (indexBuffer != null) {
			bindVertexBufferObject(gl, GL.GL_ELEMENT_ARRAY_BUFFER,
					VertexBufferData.INDEX_ID, indexBuffer.remaining(), idBuffer,
					indexBuffer,"Index VBO");
		}

		

	}
	
	
	
	
	

	private void bindVertexBufferObject(GL gl, int GL_TARGET_ARRAY, int id,
			int numObjects, IntBuffer idBuffer, Buffer buffer,String bufferDesc) {

		int allocatedBufferSize[] = new int[1];
		int bufferSize = 0;
		int bufferID = 0;
		enabledArray[id] = true;

		if (GL_TARGET_ARRAY == GL.GL_ELEMENT_ARRAY_BUFFER)
			bufferSize = numObjects * SIZEOF_INT;
		else
			bufferSize = numObjects * SIZEOF_FLOAT;
		bufferID = idBuffer.get(id);

		gl.glBindBuffer(GL_TARGET_ARRAY, bufferID);
		out.printf("Atempting to allocated "+ bufferSize + " bytes for "+bufferDesc+"...");
		gl.glBufferData(GL_TARGET_ARRAY, bufferSize, buffer, GL.GL_STATIC_DRAW);
		gl.glGetBufferParameteriv(GL_TARGET_ARRAY, GL.GL_BUFFER_SIZE,
				allocatedBufferSize, 0);
		if (allocatedBufferSize[0] <= 0)
			err.println("Error: Failed to allocate memory!");
		else if (allocatedBufferSize[0] != bufferSize) {
			err.println("Error: Unable to allocate requested memory size! Allocated: " + allocatedBufferSize[0]);
		} else
			out.printf("done.\n");
		
	}

	

	public int render(GL gl) {

		gl.glPushMatrix();
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, polygonType);
		if (vboData != null) {
			int indexBufferID = vboData.getIdBuffer().get(VertexBufferData.INDEX_ID);

			if (enabledArray[VertexBufferData.COLOR_ID] && colorMode)
				gl.glEnableClientState(GL.GL_COLOR_ARRAY);
			else {
				gl.glDisableClientState(GL.GL_COLOR_ARRAY);
				gl.glColor3f(0.42f, 0.34f, 0.26f);
			}
		
			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, indexBufferID);		// Index Array
			gl.glDrawElements(vboData.getPrimitiveType(), vboData.getNumIndices(),GL.GL_UNSIGNED_INT, 0);
		}
		
		gl.glPopMatrix();

		return 0;
	}

	public void processEvent(Message<?> e) {
		// TODO handle reInit event
		
			String action = e.getName();
			Object value = e.getValue();
			if (action.equals("colorMode")) {
				colorMode = !(Float.class.cast(value) == 0.0f);

			} else if (action.equals("polygonType")) {
				polygonType =  Float.class.cast(value).intValue();
			}
			else if (action.equals("reInit")) {
				reinit = true;
			}
		
	}

	public int addVertex(int idx, float xPos, float yPos, float zPos) {
		// TODO Auto-generated method stub
		return 0;
	}

	public int numTrianglesRendered() {
		// TODO Auto-generated method stub
		return 0;
	}

	public int render(GL gl, int mode, int numElements) {
		return 0;
		
	}

	public void render_frame_begin(GL gl) {
		// TODO Auto-generated method stub
		
	}

	public void render_frame_end(GL gl) {
		// TODO Auto-generated method stub
		
	}

	public void setupBuffers(int numTriangles) {
		// TODO Auto-generated method stub
		
	}


	public int addVertex(int idx, FloatBuffer vertex) {
		// TODO Auto-generated method stub
		return 0;
	}

	
	public int addVertex(int idx, float[] vertex) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int getNextTextureId() {
		// TODO Auto-generated method stub
		return 0;
	}
}
