package electrosphere.renderer;

import electrosphere.entity.CameraEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import electrosphere.main.Globals;
import electrosphere.main.Main;
import static electrosphere.main.Main.deltaTime;
import static electrosphere.main.Main.view_Range;
import electrosphere.renderer.framebuffer.Framebuffer;
import electrosphere.renderer.framebuffer.FramebufferUtils;
import electrosphere.renderer.framebuffer.Renderbuffer;
import electrosphere.renderer.ui.Widget;
import electrosphere.util.Utilities;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MAJOR;
import static org.lwjgl.glfw.GLFW.GLFW_CONTEXT_VERSION_MINOR;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR;
import static org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED;
import static org.lwjgl.glfw.GLFW.GLFW_FALSE;
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_CORE_PROFILE;
import static org.lwjgl.glfw.GLFW.GLFW_OPENGL_PROFILE;
import static org.lwjgl.glfw.GLFW.GLFW_TRANSPARENT_FRAMEBUFFER;
import static org.lwjgl.glfw.GLFW.GLFW_TRUE;
import static org.lwjgl.glfw.GLFW.glfwCreateWindow;
import static org.lwjgl.glfw.GLFW.glfwInit;
import static org.lwjgl.glfw.GLFW.glfwMakeContextCurrent;
import static org.lwjgl.glfw.GLFW.glfwMaximizeWindow;
import static org.lwjgl.glfw.GLFW.glfwPollEvents;
import static org.lwjgl.glfw.GLFW.glfwSetInputMode;
import static org.lwjgl.glfw.GLFW.glfwSwapBuffers;
import static org.lwjgl.glfw.GLFW.glfwTerminate;
import static org.lwjgl.glfw.GLFW.glfwWindowHint;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import static org.lwjgl.opengl.GL11.GL_BACK;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_FRONT;
import static org.lwjgl.opengl.GL11.GL_INT;
import static org.lwjgl.opengl.GL11.GL_LEQUAL;
import static org.lwjgl.opengl.GL11.GL_LESS;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SHORT;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TRIANGLES;
import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glCullFace;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.GL_TEXTURE1;
import static org.lwjgl.opengl.GL13.GL_TEXTURE2;
import static org.lwjgl.opengl.GL13.GL_TEXTURE3;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import org.lwjgl.opengl.GL15;
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.glGenBuffers;
import org.lwjgl.opengl.GL20;
import static org.lwjgl.opengl.GL20.GL_COMPILE_STATUS;
import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glCompileShader;
import static org.lwjgl.opengl.GL20.glCreateProgram;
import static org.lwjgl.opengl.GL20.glCreateShader;
import static org.lwjgl.opengl.GL20.glDeleteShader;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glGetShaderi;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import static org.lwjgl.opengl.GL20.glShaderSource;
import static org.lwjgl.opengl.GL20.glUniform1fv;
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.GL_FRAMEBUFFER;
import static org.lwjgl.opengl.GL30.GL_RENDERBUFFER;
import static org.lwjgl.opengl.GL30.glBindFramebuffer;
import static org.lwjgl.opengl.GL30.glBindRenderbuffer;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
import static org.lwjgl.system.MemoryUtil.NULL;

/**
 *
 * @author amaterasu
 */
public class RenderUtils {
    
    
    
    static int createScreenTextureVAO(){
        int rVal = glGenVertexArrays();
        glBindVertexArray(rVal);
        //vertices
        FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(12);
        VertexArrayBufferData.put(-1.0f);
        VertexArrayBufferData.put( 1.0f);
        
        VertexArrayBufferData.put(-1.0f);
        VertexArrayBufferData.put(-1.0f);
        
        VertexArrayBufferData.put( 1.0f);
        VertexArrayBufferData.put(-1.0f);
        
        VertexArrayBufferData.put(-1.0f);
        VertexArrayBufferData.put( 1.0f);
        
        VertexArrayBufferData.put( 1.0f);
        VertexArrayBufferData.put(-1.0f);
        
        VertexArrayBufferData.put( 1.0f);
        VertexArrayBufferData.put( 1.0f);
        VertexArrayBufferData.flip();
        int vertexBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, VertexArrayBufferData, GL_STATIC_DRAW);
        glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(0);
        
        
        
        
        
        //texture coords
        FloatBuffer TextureArrayBufferData = BufferUtils.createFloatBuffer(12);
        TextureArrayBufferData.put(0.0f);
        TextureArrayBufferData.put(1.0f);
        
        TextureArrayBufferData.put(0.0f);
        TextureArrayBufferData.put(0.0f);
        
        TextureArrayBufferData.put(1.0f);
        TextureArrayBufferData.put(0.0f);
        
        TextureArrayBufferData.put(0.0f);
        TextureArrayBufferData.put(1.0f);
        
        TextureArrayBufferData.put(1.0f);
        TextureArrayBufferData.put(0.0f);
        
        TextureArrayBufferData.put(1.0f);
        TextureArrayBufferData.put(1.0f);
        TextureArrayBufferData.flip();
        int textureCoordBuffer = glGenBuffers();
        glBindBuffer(GL_ARRAY_BUFFER, textureCoordBuffer);
        GL15.glBufferData(GL_ARRAY_BUFFER, TextureArrayBufferData, GL_STATIC_DRAW);
        glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
        glEnableVertexAttribArray(1);
        
        return rVal;
    }
    
    
    
    
    public static void recaptureScreen(){
        //Makes the window that was just created the current OS-level window context
        glfwMakeContextCurrent(Globals.window);
        //Maximize it
        glfwMaximizeWindow(Globals.window);
        //apply mouse controls state
        if(Globals.controlHandler.isMouseVisible()){
            Globals.controlHandler.showMouse();
        } else {
            Globals.controlHandler.hideMouse();
        }
    }
    
    
    
    
    
    
    
    public static Model createSkyboxModel(Material optionalMaterial){
        Model skyboxModel = new Model();
        skyboxModel.meshes = new ArrayList();
        
        skyboxModel.modelMatrix = new Matrix4f();
        
        
        
        boolean apply_lighting = false;
        boolean has_bones = false;
        
        Mesh skyboxmesh = new Mesh(){
            @Override
            public void complexDraw(boolean setShader, boolean bufferStandardUniforms, boolean bufferNonStandardUniforms, boolean useMaterial, boolean useShadowMap, boolean setBones, boolean useLight){
                if(setShader){
                    GL11.glDepthFunc(GL_LEQUAL);
                    glUseProgram(shader.shaderProgram);
                }
                
                if(useMaterial){
                    if(material == null){
                        Globals.materialDefault.apply_material(0,1);
                        Iterator<Vector3f> colorIterator = Globals.skyboxColors.iterator();
                        int counter = 0;
                        float[] temp = new float[3];
                        while(colorIterator.hasNext()){
                            Vector3f colorCurrent = colorIterator.next();
                            temp[0] = colorCurrent.x / 255.0f;
                            temp[1] = colorCurrent.y / 255.0f;
                            temp[2] = colorCurrent.z / 255.0f;
    //                        System.out.println("colors[" + counter + "] " + temp[0] + " " + temp[1] + " " + temp[2]);
                            glUniform3fv(glGetUniformLocation(shader.shaderProgram, "colors[" + counter + "]"), temp);
                            counter++;
                        }
                    }
                }
                
                glBindVertexArray(vertexArrayObject);
                
                
                
                if(bufferStandardUniforms){
                    //buffer model/view/proj matrices
                    glUniformMatrix4fv(shader.shaderVertexModelLoc, false, parent.modelMatrix.get(new float[16]));
                    glUniformMatrix4fv(shader.shaderVertexViewLoc, false, new Matrix4f(Globals.viewMatrix).scale(100).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)));
                }
                
                GL11.glDrawElements(GL_TRIANGLES, elementCount, GL_UNSIGNED_INT, 0);
                glBindVertexArray(0);
                
                if(setShader){
                    GL11.glDepthFunc(GL_LESS);
                }
            }
        };
        
        
        skyboxmesh.mesh = null;
        
        //
        //  VAO
        //
        skyboxmesh.vertexArrayObject = glGenVertexArrays();
        glBindVertexArray(skyboxmesh.vertexArrayObject);
        
        
        
        
        
        
        
        
        
        
        float[] vertexcoords = {
             100.0f, 100.0f, 100.0f,
             100.0f, 100.0f,-100.0f,
             100.0f,-100.0f, 100.0f,
             100.0f,-100.0f,-100.0f,
            -100.0f, 100.0f, 100.0f,
            -100.0f, 100.0f,-100.0f,
            -100.0f,-100.0f, 100.0f,
            -100.0f,-100.0f,-100.0f,
            
        };
        
        //
        //Buffer data to GPU
        //
        
        try {
            skyboxmesh.vertexCount = vertexcoords.length / 3;
            FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(skyboxmesh.vertexCount * 3);
            float[] temp = new float[3];
            for (int i = 0; i < skyboxmesh.vertexCount; i++) {
                temp[0] = vertexcoords[i * 3 + 0];
                temp[1] = vertexcoords[i * 3 + 1];
                temp[2] = vertexcoords[i * 3 + 2];
                VertexArrayBufferData.put(temp);
            }
            VertexArrayBufferData.flip();
            skyboxmesh.buffer_vertices(VertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        
        int[] facedata = {
            0,1,4,
            1,4,5,
            1,3,5,
            3,5,7,
            4,5,7,
            4,6,7,
            
            0,2,4,
            2,4,6,
            0,1,2,
            1,2,3,
            
            2,3,6,
            3,6,7,
            
        };
        
        //
        //  FACES
        //
        skyboxmesh.faceCount = facedata.length / 3;
        skyboxmesh.elementCount = facedata.length;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(skyboxmesh.elementCount);
        for(int i = 0; i < skyboxmesh.faceCount; i++){
            int[] temp = new int[3];
            temp[0] = facedata[i * 3 + 0];
            temp[1] = facedata[i * 3 + 1];
            temp[2] = facedata[i * 3 + 2];
            elementArrayBufferData.put(temp);
        }
        elementArrayBufferData.flip();
        skyboxmesh.buffer_faces(elementArrayBufferData);
        
        
        
        
        if(optionalMaterial != null){
            //
            //  NORMALS
            //
            try {
                skyboxmesh.normalCount = vertexcoords.length / 3;
                FloatBuffer NormalArrayBufferData;
                if(skyboxmesh.normalCount > 0){
                    NormalArrayBufferData = BufferUtils.createFloatBuffer(skyboxmesh.normalCount * 3);
                    float[] temp = new float[3];
                    for (int i = 0; i < skyboxmesh.normalCount; i++) {
                        temp[0] = vertexcoords[i * 3 + 0];
                        temp[1] = vertexcoords[i * 3 + 1];
                        temp[2] = vertexcoords[i * 3 + 2];
                        NormalArrayBufferData.put(temp);
                    }
                    NormalArrayBufferData.flip();
                    skyboxmesh.buffer_normals(NormalArrayBufferData, 3);
                }
            } catch (NullPointerException ex){
                ex.printStackTrace();
            }
            //
            //  TEXTURE COORDINATES
            //
            /*try {
                skyboxmesh.textureCoordCount = mesh.mTextureCoords(0).capacity();
                FloatBuffer TextureArrayBufferData;
                if(skyboxmesh.textureCoordCount > 0){
                    TextureArrayBufferData = BufferUtils.createFloatBuffer(skyboxmesh.textureCoordCount * 2);
                    float[] temp = new float[2];
                    for (int i = 0; i < skyboxmesh.textureCoordCount; i++) {
                        AIVector3D normal = texturecoords.get(i);
                        temp[0] = normal.x();
                        temp[1] = normal.y();
    //                        temp[2] = normal.z();
                        TextureArrayBufferData.put(temp);
                    }
                    TextureArrayBufferData.flip();
                    skyboxmesh.buffer_texture_coords(TextureArrayBufferData);
                }
            } catch (NullPointerException ex){
                ex.printStackTrace();
            }

            skyboxmesh.shader = ShaderProgram.smart_assemble_shader(has_bones, apply_lighting);

            skybox_model.materials.add(optionalMaterial);
            */
        } else {
            skyboxmesh.shader = ShaderProgram.loadSpecificShader("/Shaders/skybox/VertexShaderNoTexture.vs", "/Shaders/skybox/FragmentShaderNoTexture.fs");
                try {
                FloatBuffer ColorArrayBufferData = BufferUtils.createFloatBuffer(skyboxmesh.vertexCount);
                for (int i = 0; i < skyboxmesh.vertexCount; i++) {
                    ColorArrayBufferData.put(i);
                }
                ColorArrayBufferData.flip();
                int idBuffer = glGenBuffers();
                glBindBuffer(GL_ARRAY_BUFFER, idBuffer);
                GL15.glBufferData(GL_ARRAY_BUFFER, ColorArrayBufferData, GL_STATIC_DRAW);
                glVertexAttribPointer(1, 1, GL11.GL_FLOAT, false, 0, 0);
                glEnableVertexAttribArray(1);
            } catch (NullPointerException ex){
                ex.printStackTrace();
            }
        }
        
        
        
        
        skyboxmesh.hasBones = false;
        
        
        
        
        glBindVertexArray(0);
        
        
        
        
        
        
        
        
        
        skyboxmesh.nodeID = "skybox";
        
        skyboxmesh.parent = skyboxModel;
        
        
        skyboxModel.meshes.add(skyboxmesh);
        
        
        return skyboxModel;
    }
    
    
    public static Model createParticleModel(){
        Model particleModel = new Model();
        particleModel.meshes = new ArrayList();
        particleModel.modelMatrix = new Matrix4f();
        
        
        Mesh particleMesh = new Mesh();
        
        
        particleMesh.mesh = null;
        
        //
        //  VAO
        //
        particleMesh.vertexArrayObject = glGenVertexArrays();
        glBindVertexArray(particleMesh.vertexArrayObject);
        
        
        
        
        
        
        
        
        
        
        float[] vertexcoords = {
            -1.0f,  1.0f, 0.0f,
             1.0f,  1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
            
        };
        
        //
        //Buffer data to GPU
        //
        
        try {
            particleMesh.vertexCount = vertexcoords.length / 3;
            FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(particleMesh.vertexCount * 3);
            float[] temp = new float[3];
            for (int i = 0; i < particleMesh.vertexCount; i++) {
                temp[0] = vertexcoords[i * 3 + 0];
                temp[1] = vertexcoords[i * 3 + 1];
                temp[2] = vertexcoords[i * 3 + 2];
                VertexArrayBufferData.put(temp);
            }
            VertexArrayBufferData.flip();
            particleMesh.buffer_vertices(VertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        
        int[] facedata = {
            0,1,2,
            1,2,3,
        };
        
        //
        //  FACES
        //
        particleMesh.faceCount = facedata.length / 3;
        particleMesh.elementCount = facedata.length;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(particleMesh.elementCount);
        for(int i = 0; i < particleMesh.faceCount; i++){
            int[] temp = new int[3];
            temp[0] = facedata[i * 3 + 0];
            temp[1] = facedata[i * 3 + 1];
            temp[2] = facedata[i * 3 + 2];
            elementArrayBufferData.put(temp);
        }
        elementArrayBufferData.flip();
        particleMesh.buffer_faces(elementArrayBufferData);
        
        //
        //  TEXTURE COORDS
        //
        FloatBuffer texture_coords = BufferUtils.createFloatBuffer(8);
        float[] texturedata = {
            0,0,
            1,0,
            0,1,
            1,1
        };
        texture_coords.put(texturedata);
        texture_coords.flip();
        particleMesh.buffer_texture_coords(texture_coords, 2);
        
        
        
        
        particleMesh.shader = ShaderProgram.loadSpecificShader("Shaders/particleBillboard/particleBillboard.vs", "Shaders/particleBillboard/particleBillboard.fs");
        particleMesh.hasBones = false;
        
        
        
        
        glBindVertexArray(0);
        
        
        
        
        
        
        
        
        
        particleMesh.nodeID = "particleBillboard";
        
        particleMesh.parent = particleModel;
        
        
        particleModel.meshes.add(particleMesh);
        
        
        return particleModel;
    }
    
    
    public static Model createPlaneModel(){
        Model rVal = new Model();
        
        rVal.meshes = new ArrayList();
        rVal.modelMatrix = new Matrix4f();
        
        
        Mesh planeMesh = new Mesh();
        
        
        planeMesh.mesh = null;
        
        //
        //  VAO
        //
        planeMesh.vertexArrayObject = glGenVertexArrays();
        glBindVertexArray(planeMesh.vertexArrayObject);
        
        
        
        
        
        
        
        
        
        
        float[] vertexcoords = {
            -1.0f,  1.0f, 0.0f,
             1.0f,  1.0f, 0.0f,
            -1.0f, -1.0f, 0.0f,
             1.0f, -1.0f, 0.0f,
            
        };
        
        //
        //Buffer data to GPU
        //
        
        try {
            planeMesh.vertexCount = vertexcoords.length / 3;
            FloatBuffer VertexArrayBufferData = BufferUtils.createFloatBuffer(planeMesh.vertexCount * 3);
            float[] temp = new float[3];
            for (int i = 0; i < planeMesh.vertexCount; i++) {
                temp[0] = vertexcoords[i * 3 + 0];
                temp[1] = vertexcoords[i * 3 + 1];
                temp[2] = vertexcoords[i * 3 + 2];
                VertexArrayBufferData.put(temp);
            }
            VertexArrayBufferData.flip();
            planeMesh.buffer_vertices(VertexArrayBufferData, 3);
        } catch (NullPointerException ex){
            ex.printStackTrace();
        }
        
        int[] facedata = {
            0,1,2,
            1,2,3,
        };
        
        //
        //  FACES
        //
        planeMesh.faceCount = facedata.length / 3;
        planeMesh.elementCount = facedata.length;
        IntBuffer elementArrayBufferData = BufferUtils.createIntBuffer(planeMesh.elementCount);
        for(int i = 0; i < planeMesh.faceCount; i++){
            int[] temp = new int[3];
            temp[0] = facedata[i * 3 + 0];
            temp[1] = facedata[i * 3 + 1];
            temp[2] = facedata[i * 3 + 2];
            elementArrayBufferData.put(temp);
        }
        elementArrayBufferData.flip();
        planeMesh.buffer_faces(elementArrayBufferData);
        
        //
        //  TEXTURE COORDS
        //
        FloatBuffer texture_coords = BufferUtils.createFloatBuffer(8);
        float[] texturedata = {
            0,1,
            1,1,
            0,0,
            1,0
        };
        texture_coords.put(texturedata);
        texture_coords.flip();
        planeMesh.buffer_texture_coords(texture_coords, 2);
        
        
        
        
        planeMesh.shader = ShaderProgram.loadSpecificShader("Shaders/plane/plane.vs", "Shaders/plane/plane.fs");
        planeMesh.hasBones = false;
        
        
        
        
        glBindVertexArray(0);
        
        
        
        
        
        
        
        
        
        planeMesh.nodeID = "plane";
        
        planeMesh.parent = rVal;
        
        
        rVal.meshes.add(planeMesh);
        
        
        return rVal;
    }
    
}
