package electrosphere.renderer;

import electrosphere.entity.CameraEntityUtils;
import electrosphere.main.Globals;
import electrosphere.main.Main;
import electrosphere.renderer.light.LightBuffer;
import electrosphere.renderer.texture.Texture;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.assimp.AIBone;
import org.lwjgl.assimp.AIFace;
import org.lwjgl.assimp.AIMesh;
import org.lwjgl.assimp.AIVector2D;
import org.lwjgl.assimp.AIVector3D;
import org.lwjgl.assimp.AIVertexWeight;
import static org.lwjgl.opengl.ARBVertexBufferObject.*;
import org.lwjgl.opengl.GL11;
import static org.lwjgl.opengl.GL11.GL_FALSE;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_INT;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import org.lwjgl.opengl.GL15;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_ELEMENT_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import org.lwjgl.opengl.GL20;
import static org.lwjgl.opengl.GL20.*;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glUniform3fv;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL20.glUseProgram;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;

/**
 *
 * @author satellite
 */
public class Mesh {
    public Model parent;
    public String nodeID;
    public AIMesh mesh;
    public int vertexBuffer;
    public int normalBuffer;
    public int elementArrayBuffer;
    public int elementCount;
    public int faceCount;
    public int vertexArrayObject;
    public int vertexCount;
    public int normalCount;
    public int boneWeightBuffer;
    public int boneIndexBuffer;
    public int boneCount;
    public int textureCoordBuffer;
    public int textureCoordCount;
    public ArrayList<Bone> bones = new ArrayList();
    public ArrayList<String> bone_id_list = new ArrayList();
    HashMap<String,Object> uniforms = new HashMap();
    int bone_map_size = 0;
    boolean hasBones = true;
    public boolean hasTextureCoords = true;
    
    public List<Texture> textureList = new ArrayList();
    public boolean useTextureList;
    public String textureListArrayUniformName;
    
    public float vertexMinX;
    public float vertexMaxX;
    public float vertexMinY;
    public float vertexMaxY;
    public float vertexMinZ;
    public float vertexMaxZ;
    
    public ShaderProgram shader;
    
    int shaderBoneArrayLocation;
    
    Material material;
    
    LightBuffer light_buffer;
    
    public static Mesh create_mesh_from_aimesh(AIMesh mesh){
        boolean has_bones = false;
        boolean apply_lighting = true;
        
        Mesh rVal = new Mesh();
        rVal.mesh = mesh;
        
        //
        //  VAO
        //
        rVal.vertexArrayObject = glGenVertexArrays();
        glBindVertexArray(rVal.vertexArrayObject);
        
        
        
        //Basic checks
        //check num vertices
        int numVertices = 0;
        AIVector3D.Buffer vertexData = mesh.mVertices();
        while(vertexData.hasRemaining()){
            vertexData.get();
            numVertices++;
        }
        vertexData = vertexData.rewind();
        //check num normals
        int numNormals = 0;
        AIVector3D.Buffer normalData = mesh.mNormals();
        while(normalData.hasRemaining()){
            normalData.get();
            numNormals++;
        }
        normalData.rewind();
        if(numVertices != numNormals){
            System.out.println("Catastrophic failure: Number of vertices =/= Number of normals");
            System.exit(1);
        }
        
        
        
        
        
        
        
        
        
        
        
        
        //
        //Buffer data to GPU
        //
        vertexData.rewind();
        
        try {
            rVal.vertexCount = mesh.mNumVertices();
            FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(rVal.vertexCount * 3);
            float[] temp = new float[3];
            boolean definedDimensions = false;
            float minX = 0, maxX = 0, minY = 0, maxY = 0, minZ = 0, maxZ = 0;
            for (int i = 0; i < rVal.vertexCount; i++) {
                AIVector3D vertex = vertexData.get();
                float x = vertex.x();
                float y = vertex.y();
                float z = vertex.z();
                if(definedDimensions){
                    if(x < minX){ minX = x; }
                    if(x > maxX){ maxX = x; }
                    if(y < minY){ minY = y; }
                    if(y > maxY){ maxY = y; }
                    if(z < minZ){ minZ = z; }
                    if(z > maxZ){ maxZ = z; }
                } else {
                    definedDimensions = true;
                    minX = maxX = x;
                    minY = maxY = y;
                    minZ = maxZ = z;
                }
                temp[0] = x;
                temp[1] = y;
                temp[2] = z;
                VertexArrayBufferData.put(temp);
            }
            
            rVal.vertexMaxX = maxX;
            rVal.vertexMinX = minX;
            rVal.vertexMaxY = maxY;
            rVal.vertexMinY = minY;
            rVal.vertexMaxZ = maxZ;
            rVal.vertexMinZ = minZ;
            
            VertexArrayBufferData.flip();
            rVal.buffer_vertices(VertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        //
        //  NORMALS
        //
        AIVector3D.Buffer normals = mesh.mNormals();
        try {
            rVal.normalCount = mesh.mNumVertices();
            FloatBuffer NormalArrayBufferData;
            if(rVal.normalCount > 0){
                NormalArrayBufferData = BufferUtils.createFloatBuffer(rVal.normalCount * 3);
                float[] temp = new float[3];
                for (int i = 0; i < rVal.normalCount; i++) {
                    AIVector3D normal = normals.get(i);
                    temp[0] = normal.x();
                    temp[1] = normal.y();
                    temp[2] = normal.z();
                    NormalArrayBufferData.put(temp);
                }
                NormalArrayBufferData.flip();
                rVal.buffer_normals(NormalArrayBufferData, 3);
            }
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        //
        //  FACES
        //
        rVal.faceCount = mesh.mNumFaces();
        rVal.elementCount = rVal.faceCount * 3;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(rVal.elementCount);
        AIFace.Buffer facesBuffer = mesh.mFaces();
        for(int i = 0; i < rVal.faceCount; i++){
            AIFace face = facesBuffer.get(i);
            if(face.mNumIndices() != 3){
                throw new IllegalStateException("AIFace.mNumIndices() != 3");
            }
            elementArrayBufferData.put(face.mIndices());
        }
        elementArrayBufferData.flip();
        rVal.buffer_faces(elementArrayBufferData);
        
        
        
        
        
        //
        //  TEXTURE COORDINATES
        //
        if(mesh.mTextureCoords().capacity() > 0){
            AIVector3D.Buffer texturecoords = mesh.mTextureCoords(0);
            try {
                rVal.textureCoordCount = mesh.mTextureCoords(0).capacity();
                FloatBuffer TextureArrayBufferData;
                if(rVal.textureCoordCount > 0){
                    TextureArrayBufferData = BufferUtils.createFloatBuffer(rVal.textureCoordCount * 2);
                    float[] temp = new float[2];
                    for (int i = 0; i < rVal.textureCoordCount; i++) {
                        AIVector3D normal = texturecoords.get(i);
                        temp[0] = normal.x();
                        temp[1] = normal.y();
//                        temp[2] = normal.z();
                        TextureArrayBufferData.put(temp);
                    }
                    TextureArrayBufferData.flip();
                    rVal.buffer_texture_coords(TextureArrayBufferData, 2);
                }
            } catch (NullPointerException ex){
                ex.printStackTrace();
            }
            //System.out.println("Enabled texture coordinates");
        }
        
        
        
        
        
        
        
        
        
        
        
        
        
        //
        //Read in bones
        //AND buffer data (weights) to GPU
        //
        PointerBuffer boneBuffer = mesh.mBones();
        if(boneBuffer != null){
            has_bones = true;
            while(boneBuffer.hasRemaining()){
                long currentAddr = boneBuffer.get();
                AIBone currentBoneData = AIBone.createSafe(currentAddr);
//                System.out.println("Num weights: " + currentBoneData.mNumWeights());
                Bone currentBone = new Bone();
                currentBone.boneID = currentBoneData.mName().dataString();
                currentBone.inverseBindPoseMatrix = electrosphere.util.Utilities.convertAIMatrix(currentBoneData.mOffsetMatrix());
                currentBone.numWeights = currentBoneData.mNumWeights();
                currentBone.weights = new HashMap();
                Iterator<AIVertexWeight> weightIterator = currentBoneData.mWeights().iterator();
                while(weightIterator.hasNext()){
                    AIVertexWeight currentWeightData = weightIterator.next();
                    currentBone.weights.put(currentWeightData.mVertexId(), currentWeightData.mWeight());
                }
                rVal.bones.add(currentBone);
                rVal.bone_id_list.add(currentBone.boneID);
            }
            rVal.boneCount = rVal.bones.size();
            FloatBuffer boneWeightDataBuffer = BufferUtils.createFloatBuffer(4 * rVal.vertexCount);//FloatBuffer.allocate(4 * vertexCount);
            FloatBuffer boneIndexDataBuffer = BufferUtils.createFloatBuffer(4 * rVal.vertexCount);//IntBuffer.allocate(4 * vertexCount);
            Iterator<Bone> boneIterator;
            for(int i = 0; i < rVal.vertexCount; i++){
                float[] weight = new float[4];
                float[] index = new float[4];
                int boneCounter = 0;
                boneIterator = rVal.bones.iterator();
                while(boneIterator.hasNext()){
                    Bone currentBone = boneIterator.next();
                    float boneVal = 0;
                    if(currentBone.weights.get(i) != null){
                        boneVal = currentBone.weights.get(i);
                    }
                    if(boneVal > 0){
                        if(boneVal > weight[0]){
                            weight[3] = weight[2];
                            weight[2] = weight[1];
                            weight[1] = weight[0];
                            weight[0] = boneVal;
                            index[3] = index[2];
                            index[2] = index[1];
                            index[1] = index[0];
                            index[0] = boneCounter;
                        } else if(boneVal > weight[1]){
                            weight[3] = weight[2];
                            weight[2] = weight[1];
                            weight[1] = boneVal;
                            index[3] = index[2];
                            index[2] = index[1];
                            index[1] = boneCounter;
                        } else if(boneVal > weight[2]){
                            weight[3] = weight[2];
                            weight[2] = boneVal;
                            index[3] = index[2];
                            index[2] = boneCounter;
                        } else if(boneVal > weight[3]){
                            weight[3] = boneVal;
                            index[3] = boneCounter;
                        }
                    }
                    boneCounter++;
                }
                float total = weight[0] + weight[1] + weight[2] + weight[3];
                if(total != 1.0f){
                    weight[0] = weight[0] * (1.0f / total);
                    weight[1] = weight[1] * (1.0f / total);
                    weight[2] = weight[2] * (1.0f / total);
                    weight[3] = weight[3] * (1.0f / total);
                }
                boneIndexDataBuffer.put(index);
                boneWeightDataBuffer.put(weight);
            }
            boneIndexDataBuffer.flip();
            boneWeightDataBuffer.flip();
            
            
            rVal.boneWeightBuffer = glGenBuffers();
            glBindBuffer(GL_ARRAY_BUFFER, rVal.boneWeightBuffer);
            GL15.glBufferData(GL_ARRAY_BUFFER, boneWeightDataBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(2, 4, GL_FLOAT, false, 0, 0);
            glEnableVertexAttribArray(2);
            
            
            rVal.boneIndexBuffer = glGenBuffers();
            glBindBuffer(GL_ARRAY_BUFFER, rVal.boneIndexBuffer);
            GL15.glBufferData(GL_ARRAY_BUFFER, boneIndexDataBuffer, GL_STATIC_DRAW);
            glVertexAttribPointer(3, 4, GL_FLOAT, false, 0, 0);
            glEnableVertexAttribArray(3);
        } else {
            rVal.hasBones = false;
        }
        
        
        
        
        
        
        
        
        
        
        
        
        rVal.shader = ShaderProgram.smart_assemble_shader(has_bones, apply_lighting);
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        glBindVertexArray(0);
        
        
        
        
        
        
        
        
        
        rVal.nodeID = mesh.mName().dataString();
        
        
        return rVal;
    }
    
    
    public Mesh(){
        
    }
    
//    public void draw() {
//        glUseProgram(shader.shaderProgram);
//        
//        //Until we switch to uniform buffer objects we will have to buffer lighting data here manually each time we draw
//        //side note:    :(
//        if(light_buffer == null){
//            float temp[] = new float[3];
//            temp[0] = 0.2f;
//            temp[1] = -1.0f;
//            temp[2] = 0.3f;
//            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.direction"), temp);
//
//            temp[0] = 0.1f;
//            temp[1] = 0.1f;
//            temp[2] = 0.1f;
//            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.ambient"), temp);
//
//            temp[0] = 0.8f;
//            temp[1] = 0.8f;
//            temp[2] = 0.8f;
//            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.diffuse"), temp);
//            
//            temp[0] = 0.1f;
//            temp[1] = 0.1f;
//            temp[2] = 0.1f;
//            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.specular"), temp); 
//            
//            temp[0] = 32f;
//            glUniform1fv(glGetUniformLocation(shader.shaderProgram, "material.shininess"), temp);
//            
//            GL20.glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, "model"), false, parent.modelMatrix.get(new float[16]));
//
//            Vector3f cam_Loc = CameraEntityUtils.getCameraEye(Globals.playerCamera);//Globals.cameraVisible.pos_Center;
//            temp[0] = cam_Loc.x;
//            temp[1] = cam_Loc.y;
//            temp[2] = cam_Loc.z;
//            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "viewPos"), temp);
//        } else {
//            GL20.glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, "model"), false, parent.modelMatrix.get(new float[16]));
//        }
//        
//        
//        if(material == null){
//            Globals.materialDefault.apply_material(0,1);
//            GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 0);
//        } else {
//            material.apply_material();
//            if(material.hasTransparency){
//                GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 1);
//            } else {
//                GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 1);
//            }
//        }
//        
//        
//        
//        glBindVertexArray(vertexArrayObject);
//        
//        
//        glActiveTexture(GL_TEXTURE3);
//        glBindTexture(GL_TEXTURE_2D, Globals.shadowMapTextureLoc);
//        glUniform1i(glGetUniformLocation(shader.shaderProgram, "shadowMap"), 3);
//        
//        
//        
//        //
//        //Handle bones
//        //
//        if(bones != null && !bones.isEmpty()){
//            glUniform1i(shader.shaderVertexHasBonesLoc, 1);
//            glUniform1i(shader.shaderVertexNumBonesLoc, bones.size());
//            Iterator<String> boneIterator = bone_id_list.iterator();
//            float bufferarray[] = new float[16];
//            int incrementer = 0;
//            while (boneIterator.hasNext()){
//                Bone currentBone = parent.boneMap.get(boneIterator.next());
//                Matrix4f currentMat = new Matrix4f(currentBone.final_transform);
//                currentMat.get(bufferarray);
//                String currentUniform = "bones[" + incrementer + "]";
//                GL20.glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, currentUniform), false, bufferarray);
//                incrementer++;
//            }
//        } else {
//            glUniform1i(shader.shaderVertexHasBonesLoc, 0);
//        }
//        
//        
//        
//        //buffer model/view/proj matrices
//        glUniformMatrix4fv(shader.shaderVertexModelLoc, false, parent.modelMatrix.get(new float[16]));
//        glUniformMatrix4fv(shader.shaderVertexViewLoc, false, Globals.viewMatrix.get(new float[16]));
//        glUniformMatrix4fv(shader.shaderVertexProjectionLoc, false, Globals.projectionMatrix.get(new float[16]));
//        glUniform3fv(shader.shaderVertexViewPosLoc, CameraEntityUtils.getCameraEye(Globals.playerCamera).get(BufferUtils.createFloatBuffer(3)));
//        glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, "lightSpaceMatrix"), false, Globals.lightDepthMatrix.get(new float[16]));
//        
//        //
//        //
//        //Testing Lights
//        //
//        //
//        float test_Light_Data[] = new float[3];
//        test_Light_Data[0] = 0.2f;
//        test_Light_Data[1] = -1.0f;
//        test_Light_Data[2] = 0.3f;
//        glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.direction"), test_Light_Data);
//        
//        test_Light_Data = new float[3];
//        test_Light_Data[0] = 0.3f;
//        test_Light_Data[1] = 0.3f;
//        test_Light_Data[2] = 0.3f;
//        glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.ambient"), test_Light_Data);
//        
//        test_Light_Data = new float[3];
//        test_Light_Data[0] = 0.5f;
//        test_Light_Data[1] = 0.5f;
//        test_Light_Data[2] = 0.5f;
//        glUniform3fv(glGetUniformLocation(shader.shaderProgram, "dirLight.diffuse"), test_Light_Data);
//        
//        Vector3f cam_Loc = CameraEntityUtils.getCameraEye(Globals.playerCamera);
//        test_Light_Data = new float[3];
//        test_Light_Data[0] = cam_Loc.x;
//        test_Light_Data[1] = cam_Loc.y;
//        test_Light_Data[2] = cam_Loc.z;
//        glUniform3fv(glGetUniformLocation(shader.shaderProgram, "viewPos"), test_Light_Data);
//        
//        
//        GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
//        glBindVertexArray(0);
//    }
    
    public void buffer_vertices(FloatBuffer verticies, int vertexDimension){
        vertexBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, verticies, GL_STATIC_DRAW);
        glVertexAttribPointer(0, vertexDimension, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(0);
    }
    
    public void buffer_normals(FloatBuffer normals, int normalDimension){
        normalBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, normalBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, normals, GL_STATIC_DRAW);
        glVertexAttribPointer(1, normalDimension, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(1);
    }
    
    public void buffer_faces(IntBuffer faces){
        elementArrayBuffer = glGenBuffers();
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBuffer);
        GL15.glBufferData(GL_ELEMENT_ARRAY_BUFFER, faces, GL_STATIC_DRAW);
        elementCount = faces.capacity();
    }
    
    public void buffer_texture_coords(FloatBuffer coords, int textureDimension){
        textureCoordBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, textureCoordBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, coords, GL_STATIC_DRAW);
        glVertexAttribPointer(4, textureDimension, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(4);
    }
    
    public void bufferCustomFloatAttribArray(FloatBuffer buffer, int bufferDimension, int attribIndex){
        int customBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, customBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
        glVertexAttribPointer(attribIndex, bufferDimension, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(attribIndex);
    }
    
    public void bufferCustomIntAttribArray(IntBuffer buffer, int bufferDimension, int attribIndex){
        int customBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, customBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
        glVertexAttribPointer(attribIndex, bufferDimension, GL_INT, false, 0, 0);
        glEnableVertexAttribArray(attribIndex);
    }
    
    public void bufferCustomUIntAttribArray(IntBuffer buffer, int bufferDimension, int attribIndex){
        int customBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, customBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
        glVertexAttribPointer(attribIndex, bufferDimension, GL_UNSIGNED_INT, false, 0, 0);
        glEnableVertexAttribArray(attribIndex);
    }
    
    public void setTextureList(List<Texture> textureList, String uniformName){
        this.textureList = textureList;
        useTextureList = true;
        textureListArrayUniformName = uniformName;
    }
    
    public void setMaterial(Material input){
        this.material = input;
    }
    
    
//    public void drawForDepthBuffer(){
//        /*
//        
//        !!!!!!!!!!!!!!!!!THERE IS NO SHADER PROGRAM HERE!!!!!!!!!!!!!!!!!!!!!!!!!!
//        
//        The shader program is set in the main render function to make things easier
//        
//        */
//        
//        
//        glBindVertexArray(vertexArrayObject);
//        
//        
//        
//        
//        //
//        //Handle bones
//        //
//        if(bones != null && !bones.isEmpty()){
//            glUniform1i(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "numBones"), bones.size());
//            glUniform1i(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "hasBones"), 1);
//            Iterator<String> boneIterator = bone_id_list.iterator();
//            float bufferarray[] = new float[16];
//            int incrementer = 0;
//            while (boneIterator.hasNext()){
//                Bone currentBone = parent.boneMap.get(boneIterator.next());
//                Matrix4f currentMat = new Matrix4f(currentBone.final_transform);
//                currentMat.get(bufferarray);
//                String currentUniform = "bones[" + incrementer + "]";
//                GL20.glUniformMatrix4fv(glGetUniformLocation(Globals.depthMapShaderProgramLoc, currentUniform), false, bufferarray);
//                incrementer++;
//            }
//        } else {
//            glUniform1i(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "hasBones"), 0);
//        }
//        
//        //buffer model/view/proj matrices
//        glUniformMatrix4fv(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "model"), false, parent.modelMatrix.get(new float[16]));
//        glUniformMatrix4fv(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "lightSpaceMatrix"), false, Globals.lightDepthMatrix.get(new float[16]));
//        
//        
//        GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
//        glBindVertexArray(0);
//    }
    
//    public void drawUI(){
//        glUseProgram(shader.shaderProgram);
//        
//        glBindVertexArray(vertexArrayObject);
//        
//        if(material == null){
//            Globals.materialDefault.apply_material(0,1);
//            GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 0);
//        } else {
//            material.apply_material();
//            if(material.hasTransparency){
//                GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 1);
//            } else {
//                GL20.glUniform1i(glGetUniformLocation(shader.shaderProgram, "hasTransparency"), 1);
//            }
//        }
//        
//        //buffers contents of uniforms map to gpu
//        bufferAllUniforms();
//        
//        glUniformMatrix4fv(glGetUniformLocation(shader.shaderProgram, "model"), false, parent.modelMatrix.get(new float[16]));
//        
//        GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
//        glBindVertexArray(0);
//    }
    
    public void setUniform(String key, Object o){
        uniforms.put(key, o);
    }
    
    void bufferAllUniforms(){
        for(String key : uniforms.keySet()){
            Object currentUniformRaw = uniforms.get(key);
            if(currentUniformRaw instanceof Matrix4f){
                Matrix4f currentUniform = (Matrix4f)currentUniformRaw;
                glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, key), false, currentUniform.get(new float[16]));
            }
            if(currentUniformRaw instanceof Vector3f){
                Vector3f currentUniform = (Vector3f)currentUniformRaw;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, key), currentUniform.get(BufferUtils.createFloatBuffer(3)));
            }
            if(currentUniformRaw instanceof Integer){
                int currentInform = (Integer)currentUniformRaw;
                glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, key), currentInform);
            }
        }
    }
    
    
    public void complexDraw(boolean setShader, boolean bufferStandardUniforms, boolean bufferNonStandardUniforms, boolean useMaterial, boolean useShadowMap, boolean setBones, boolean useLight){
        if(setShader){
            Globals.renderingEngine.setActiveShader(shader);
        }
        
        if(useLight){
            //Until we switch to uniform buffer objects we will have to buffer lighting data here manually each time we draw
            //side note:    :(
            if(light_buffer == null){
                float temp[] = new float[3];
                temp[0] = 0.2f;
                temp[1] = -1.0f;
                temp[2] = 0.3f;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.direction"), temp);

                temp[0] = 0.1f;
                temp[1] = 0.1f;
                temp[2] = 0.1f;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.ambient"), temp);

                temp[0] = 0.8f;
                temp[1] = 0.8f;
                temp[2] = 0.8f;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.diffuse"), temp);

                temp[0] = 0.1f;
                temp[1] = 0.1f;
                temp[2] = 0.1f;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.specular"), temp); 

                temp[0] = 32f;
                glUniform1fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "material.shininess"), temp);

                GL20.glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "model"), false, parent.modelMatrix.get(new float[16]));

                Vector3f cam_Loc = CameraEntityUtils.getCameraEye(Globals.playerCamera);//Globals.cameraVisible.pos_Center;
                temp[0] = cam_Loc.x;
                temp[1] = cam_Loc.y;
                temp[2] = cam_Loc.z;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "viewPos"), temp);
            } else {
                GL20.glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "model"), false, parent.modelMatrix.get(new float[16]));
            }
        }
        
        if(useMaterial && !useTextureList){
            if(material == null){
                Globals.materialDefault.apply_material(0,1);
                GL20.glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "hasTransparency"), 0);
            } else {
                material.apply_material();
                if(material.hasTransparency){
                    GL20.glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "hasTransparency"), 1);
                } else {
                    GL20.glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "hasTransparency"), 1);
                }
            }
        }
        
        
        
        
        
        glBindVertexArray(vertexArrayObject);
        
        
        
        if(useTextureList){
            int i = 0;
//            glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "groundTextures"), 5);
//            for(int j = 1; j < 15; j++){
//                textureList.get(0).bind(j);
//            }
            for(Texture texture : textureList){
//                System.out.println(texture.getPath() + " => groundTextures[" + i + "]" + "=>" + (i));
                if(texture != null){
                    texture.bind(5+i);
                }
                glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "groundTextures[" + i + "]"),5+i);
                i++;
            }
            for(int j = i; j < 10; j++){
                glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "groundTextures[" + j + "]"),5+j);
            }
//            glActiveTexture(GL_TEXTURE0);
        }
        
        if(useShadowMap){
            glActiveTexture(GL_TEXTURE3);
            glBindTexture(GL_TEXTURE_2D, Globals.shadowMapTextureLoc);
            glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "shadowMap"), 3);
        }
        
        
        if(setBones){
            //
            //Handle bones
            //
            if(bones != null && !bones.isEmpty()){
                glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "hasBones"), 1);
                glUniform1i(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "numBones"), bones.size());
                Iterator<String> boneIterator = bone_id_list.iterator();
                float bufferarray[] = new float[16];
                int incrementer = 0;
                while (boneIterator.hasNext()){
                    Bone currentBone = parent.boneMap.get(boneIterator.next());
                    Matrix4f currentMat = new Matrix4f(currentBone.final_transform);
                    currentMat.get(bufferarray);
                    String currentUniform = "bones[" + incrementer + "]";
                    GL20.glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, currentUniform), false, bufferarray);
                    incrementer++;
                }
            } else {
                glUniform1i(Globals.renderingEngine.getActiveShader().shaderVertexHasBonesLoc, 0);
            }
        } else {
            glUniform1i(Globals.renderingEngine.getActiveShader().shaderVertexHasBonesLoc, 0);
        }
        
        
        if(bufferStandardUniforms){
            //buffer model/view/proj matrices
            glUniformMatrix4fv(Globals.renderingEngine.getActiveShader().shaderVertexModelLoc, false, parent.modelMatrix.get(new float[16]));
            glUniformMatrix4fv(Globals.renderingEngine.getActiveShader().shaderVertexViewLoc, false, Globals.viewMatrix.get(new float[16]));
            glUniformMatrix4fv(Globals.renderingEngine.getActiveShader().shaderVertexProjectionLoc, false, Globals.projectionMatrix.get(new float[16]));
            glUniform3fv(Globals.renderingEngine.getActiveShader().shaderVertexViewPosLoc, CameraEntityUtils.getCameraEye(Globals.playerCamera).get(BufferUtils.createFloatBuffer(3)));
            glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "lightSpaceMatrix"), false, Globals.lightDepthMatrix.get(new float[16]));
        }
        
        if(bufferNonStandardUniforms){
            bufferAllUniforms();
        }
        
//        if(shadowMapShader){
//            glUniformMatrix4fv(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "model"), false, parent.modelMatrix.get(new float[16]));
//            glUniformMatrix4fv(glGetUniformLocation(Globals.depthMapShaderProgramLoc, "lightSpaceMatrix"), false, Globals.lightDepthMatrix.get(new float[16]));
//        }
        
        if(useLight){
            //
            //
            //Testing Lights
            //
            //
            float test_Light_Data[] = new float[3];
            test_Light_Data[0] = 0.2f;
            test_Light_Data[1] = -1.0f;
            test_Light_Data[2] = 0.3f;
            glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.direction"), test_Light_Data);

            test_Light_Data = new float[3];
            test_Light_Data[0] = 0.3f;
            test_Light_Data[1] = 0.3f;
            test_Light_Data[2] = 0.3f;
            glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.ambient"), test_Light_Data);

            test_Light_Data = new float[3];
            test_Light_Data[0] = 0.5f;
            test_Light_Data[1] = 0.5f;
            test_Light_Data[2] = 0.5f;
            glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.diffuse"), test_Light_Data);

            Vector3f cam_Loc = CameraEntityUtils.getCameraEye(Globals.playerCamera);
            test_Light_Data = new float[3];
            test_Light_Data[0] = cam_Loc.x;
            test_Light_Data[1] = cam_Loc.y;
            test_Light_Data[2] = cam_Loc.z;
            glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "viewPos"), test_Light_Data);
        }
        
        
        
        GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
    }
}
