package com.github.hydos.ginger.engine.shadow;

import java.util.*;

import org.lwjgl.opengl.*;

import com.github.hydos.ginger.engine.elements.objects.RenderObject;
import com.github.hydos.ginger.engine.math.Maths;
import com.github.hydos.ginger.engine.math.matrixes.Matrix4f;
import com.github.hydos.ginger.engine.render.MasterRenderer;
import com.github.hydos.ginger.engine.render.models.*;

public class ShadowMapEntityRenderer
{
	private Matrix4f projectionViewMatrix;
	private ShadowShader shader;

	/** @param shader
	 *                             - the simple shader program being used for the shadow render
	 *                             pass.
	 * @param projectionViewMatrix
	 *                             - the orthographic projection matrix multiplied by the light's
	 *                             "view" matrix. */
	protected ShadowMapEntityRenderer(ShadowShader shader, Matrix4f projectionViewMatrix)
	{
		this.shader = shader;
		this.projectionViewMatrix = projectionViewMatrix;
	}

	/** Binds a raw model before rendering. Only the attribute 0 is enabled here
	 * because that is where the positions are stored in the VAO, and only the
	 * positions are required in the vertex shader.
	 * 
	 * @param rawModel
	 *                 - the model to be bound. */
	private void bindModel(RawModel rawModel)
	{
		GL30.glBindVertexArray(rawModel.getVaoID());
		GL20.glEnableVertexAttribArray(0);
		GL20.glEnableVertexAttribArray(1);
	}

	/** Prepares an entity to be rendered. The model matrix is created in the
	 * usual way and then multiplied with the projection and view matrix (often
	 * in the past we've done this in the vertex shader) to create the
	 * mvp-matrix. This is then loaded to the vertex shader as a uniform.
	 * 
	 * @param entity
	 *               - the entity to be prepared for rendering. */
	private void prepareInstance(RenderObject entity)
	{
		Matrix4f modelMatrix = Maths.createTransformationMatrix(entity.getPosition(),
			entity.getRotX(), entity.getRotY(), entity.getRotZ(), entity.getScale());
		Matrix4f mvpMatrix = Matrix4f.mul(projectionViewMatrix, modelMatrix, null);
		shader.loadMvpMatrix(mvpMatrix);
	}

	/** Renders entieis to the shadow map. Each model is first bound and then all
	 * of the entities using that model are rendered to the shadow map.
	 * 
	 * @param entities
	 *                 - the entities to be rendered to the shadow map. */
	protected void render(Map<TexturedModel, List<RenderObject>> entities)
	{
		for (TexturedModel model : entities.keySet())
		{
			RawModel rawModel = model.getRawModel();
			bindModel(rawModel);
			GL13.glActiveTexture(GL13.GL_TEXTURE0);
			GL11.glBindTexture(GL11.GL_TEXTURE_2D, model.getTexture().getTextureID());
			if (model.getTexture().isTransparent())
			{ MasterRenderer.disableCulling(); }
			for (RenderObject entity : entities.get(model))
			{
				prepareInstance(entity);
				GL11.glDrawElements(GL11.GL_TRIANGLES, rawModel.getVertexCount(),
					GL11.GL_UNSIGNED_INT, 0);
			}
			if (model.getTexture().isTransparent())
			{ MasterRenderer.enableCulling(); }
		}
		GL20.glDisableVertexAttribArray(0);
		GL20.glDisableVertexAttribArray(1);
		GL30.glBindVertexArray(0);
	}
}
