package projectzombie.model;

import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;

import java.util.Random;

import org.lwjgl.opengl.GL33;

import gl_engine.MathHelpers;
import gl_engine.matrix.Matrix4;
import gl_engine.texture.TextureRef3D;
import projectzombie.Main;
import projectzombie.init.Resources;

public abstract class Model implements IModel
{
	private static final Random rand = new Random();
	
	public static final float OFFSET = 0.00001f;
	public static final int SIZE = 16;
	public static IModel bound = null;
	
	int vao, vbo, ibo;
	boolean loaded = false;
	
	public static void setGLArrayAttributes()
	{
		// aPos
		glVertexAttribPointer(0, 3, GL_FLOAT, false, Float.BYTES * SIZE, 0);
		glEnableVertexAttribArray(0);
		
		// aTex
		glVertexAttribPointer(1, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 3);
		glEnableVertexAttribArray(1);
		
		// aTexY
		glVertexAttribPointer(2, 2, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 6);
		glEnableVertexAttribArray(2);
		
		// aOffset
		glVertexAttribPointer(3, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 8);
		glEnableVertexAttribArray(3);
		
		// aAnimate
		glVertexAttribPointer(4, 2, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 11);
		glEnableVertexAttribArray(4);
		
		// aFlags
		glVertexAttribPointer(5, 3, GL_FLOAT, false, Float.BYTES * SIZE, Float.BYTES * 13);
		glEnableVertexAttribArray(5);
	}
	
	protected void generate()
	{
		if(loaded) {
			return;
		}
		
		int[] indicies = this.getIndicies();
		float[] verticies = this.getVerticies();
		TextureRef3D[] refs = this.getTextures();
		
		if(verticies.length % SIZE != 0 || refs.length * SIZE != verticies.length) {
			System.err.println("Invalid model: " + verticies.length + ", " + refs.length + ", " + indicies.length);
			System.exit(1);
			return;
		}
		
		int size = verticies.length/SIZE;
		double k = OFFSET;
		
		for(int i=0;i<size;i++) {
			TextureRef3D ref = refs[i];
			verticies[i*SIZE + 3] = (float)MathHelpers.map(verticies[i*SIZE + 3], 0-k, 1+k, ref.sx, ref.ex);
			verticies[i*SIZE + 4] = (float)MathHelpers.map(verticies[i*SIZE + 4], 0-k, 1+k, ref.sy, ref.ey);
			verticies[i*SIZE + 5] = ref.z;
			verticies[i*SIZE + 6] = (float)MathHelpers.map(verticies[i*SIZE + 6], 0, 1, ref.sy, ref.ey);
			verticies[i*SIZE + 7] = (float)MathHelpers.map(verticies[i*SIZE + 7], 0, 1, ref.sy, ref.ey);
		}
		
		vao = glGenVertexArrays();
		vbo = glGenBuffers();
		ibo = glGenBuffers();
		
		glBindVertexArray(vao);
		
		glBindBuffer(GL33.GL_ELEMENT_ARRAY_BUFFER, ibo);
		glBufferData(GL33.GL_ELEMENT_ARRAY_BUFFER, indicies, GL33.GL_STATIC_DRAW);
		
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		glBufferData(GL_ARRAY_BUFFER, verticies, GL_STATIC_DRAW);
		
		setGLArrayAttributes();
		
		loaded = true;
	}
	
	public void bind()
	{
		if(loaded) {
			glBindVertexArray(vao);
		}
		
		else {
			generate();
		}
		
		bound = this;
	}
	
	public void render()
	{
		if(bound != this) {
			bind();
		}
		
		GL33.glDrawElements(GL33.GL_TRIANGLES, getIndexSize(), GL33.GL_UNSIGNED_INT, 0);
	}
	
	public void free() {
		GL33.glDeleteBuffers(ibo);
		GL33.glDeleteBuffers(vbo);
		GL33.glDeleteVertexArrays(vao);
		loaded = false;
	}
	
	public void setModel(Matrix4 model) {
		GL33.glUniformMatrix4fv(Main.window.glsl_model, true, model.getArray());
	}
	
	public TextureRef3D getRandomChunk(int[] tex_id)
	{
		TextureRef3D[] textures = getTextures();
		
		if(textures.length == 0) {
			return Resources.TEX_EMPTY;
		}
		
		tex_id[0] = (int)(rand.nextDouble() * textures.length);
		TextureRef3D texture = textures[tex_id[0]];
		TextureRef3D ref = new TextureRef3D();
		
		float dx = texture.ex - texture.sx;
		float dy = texture.ey - texture.sy;
		float w = (float)getWidth();
		float h = (float)getHeight();
		
		ref.sx = (float)MathHelpers.map((int)(rand.nextDouble()*16*w), 0, 16*w, texture.sx, texture.ex - dx/(16*w));
		ref.sy = (float)MathHelpers.map((int)(rand.nextDouble()*16*h), 0, 16*h, texture.sy, texture.ey - dy/(16*h));
		ref.ex = ref.sx + dx/(16*w);
		ref.ey = ref.sy + dy/(16*h);
		
		ref.texmap = texture.texmap;
		ref.z = texture.z;
		
		return ref;
	}
	
	public boolean isLoaded() {
		return loaded;
	}
}