package renderEngine;

import java.util.List;
import java.util.Map;

import javax.vecmath.Matrix4f;
import javax.vecmath.Vector2f;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.windows.DISPLAY_DEVICE;

import entities.Entity;
import models.RawModel;
import models.TexturedModel;
import shaders.StaticShader;
import textures.ModelTexture;
import toolbox.Maths;

/**
 * Handles the rendering of a model to the screen.
 * 
 * @author Karl
 *
 */
public class Renderer {

	private static final float FOV = 70;
	private static final float NEAR_PLANE = 0.1f;
	private static final float FAR_PLANE = 10000;
	
	private Matrix4f projectionMatrix;
	private StaticShader shader;
	
	public Renderer(StaticShader shader) {
		this.shader = shader;
		createProjectionMatrix();
		shader.start();
		shader.loadProjectionMatrix(projectionMatrix);
		shader.stop();
	}
	
	public void prepare() {
		GL11.glEnable(GL11.GL_CULL_FACE);
		GL11.glEnable(GL11.GL_BACK);
		GL11.glEnable(GL11.GL_DEPTH_TEST);
		GL11.glClearColor(1, 0, 0, 1);
		GL11.glClear(GL11.GL_COLOR_BUFFER_BIT|GL11.GL_DEPTH_BUFFER_BIT);
	}
	
	public void render(Map<TexturedModel, List<Entity>> entities) {
		for (TexturedModel model:entities.keySet()) {
			prepareTexturedModel(model);
			List<Entity> batch = entities.get(model);
			for (Entity entity:batch) {
				prepareInstance(entity);
				GL11.glDrawElements(GL11.GL_TRIANGLES, model.getRawModel().getVertexCount(), GL11.GL_UNSIGNED_INT, 0);
			}
			unbindTexturedModel();
		}
	}
	
	private void prepareTexturedModel(TexturedModel model) {
		RawModel rawModel = model.getRawModel();
		GL30.glBindVertexArray(rawModel.getVaoID());
		GL20.glEnableVertexAttribArray(0);
		GL20.glEnableVertexAttribArray(1);
		GL20.glEnableVertexAttribArray(2);
		ModelTexture texture = model.getTexture();
		shader.loadShineVariable(texture.getShineDamper(), texture.getReflectivity());
		GL13.glActiveTexture(GL13.GL_TEXTURE0);
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getTexture().getTexturedID());
	}
	
	private void unbindTexturedModel() {
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL20.glDisableVertexAttribArray(2);
		GL30.glBindVertexArray(0);
		
	}
	
	private void prepareInstance(Entity entity) {
		Matrix4f transformationMatrix = Maths.createTransformationMatrix(entity.getPosition(), entity.getRotation(), entity.getScale());
		shader.loadTransformationMatrix(transformationMatrix);
	}
	

	private void createProjectionMatrix() {
		//float aspectRatio = (float) Display.getWidth() / (float) Display.getHeight();
		Vector2f windowsSize = DisplayManager.getSize();
		float aspectRatio = windowsSize.x / windowsSize.y;
//		float y_scale = (float) ((1f/Math.atan(Math.toRadians(FOV/2f))) * aspectRatio);
//		float x_scale = y_scale / aspectRatio;
//		float frustrum_length = FAR_PLANE - NEAR_PLANE;
//		
//		projectionMatrix = new Matrix4f();
//		projectionMatrix.m00 = x_scale;
//		projectionMatrix.m11 = y_scale;
//		projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustrum_length);
//		projectionMatrix.m23 = -1;;
//		projectionMatrix.m32 = -((2*FAR_PLANE * NEAR_PLANE) / frustrum_length);
//		projectionMatrix.m33 = 0;
		projectionMatrix = matPerspective(FOV, aspectRatio, NEAR_PLANE, FAR_PLANE);
	}
	

	Matrix4f matFrustum(float xmin, float xmax, float ymin, float ymax, float zNear, float zFar) {
		Matrix4f tmp = new Matrix4f();
		//  0  1  2  3
		//  4  5  6  7
		//  8  9 10 11
		// 12 13 14 15
		tmp.m00  = (2.0f * zNear) / (xmax - xmin);
		tmp.m11  = (2.0f * zNear) / (ymax - ymin);
		tmp.m22 = -(zFar + zNear) / (zFar - zNear);
		tmp.m02  = (xmax + xmin) / (xmax - xmin);
		tmp.m12  = (ymax + ymin) / (ymax - ymin);
		tmp.m32 = -1.0f;
		tmp.m23 = -(2.0f * zFar * zNear) / (zFar - zNear);
		return tmp;
	}

	Matrix4f matPerspective(float fovx, float aspect, float zNear, float zFar) {
		//TK_DEBUG("drax perspective: fovx=" << fovx << "->" << aspect << "  " << zNear << "->" << zFar);
		float xmax = zNear * (float)Math.atan(fovx/2.0);
		float xmin = -xmax;
		
		float ymin = xmin / aspect;
		float ymax = xmax / aspect;
		//TK_DEBUG("drax perspective: " << xmin << "->" << xmax << " & " << ymin << "->" << ymax << " " << zNear << "->" << zFar);
		return matFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
	}

	Matrix4f matOrtho(float left, float right, float bottom, float top, float nearVal, float farVal) {
		Matrix4f tmp = new Matrix4f();
		tmp.m00 = 2.0f / (right - left);
		tmp.m11 = 2.0f / (top - bottom);
		tmp.m22 = -2.0f / (farVal - nearVal);
		tmp.m03  = -1*(right + left) / (right - left);
		tmp.m13  = -1*(top + bottom) / (top - bottom);
		tmp.m23 = -1*(farVal + nearVal) / (farVal - nearVal);
		tmp.m33 = 1;
		return tmp;
	}
	
	
}
