package electrosphere.renderer;

import static electrosphere.renderer.RenderUtils.createScreenTextureVAO;
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_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.glfwCreateWindow;
import static org.lwjgl.glfw.GLFW.glfwGetPrimaryMonitor;
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.glfwSwapBuffers;
import static org.lwjgl.glfw.GLFW.glfwTerminate;
import static org.lwjgl.glfw.GLFW.glfwWindowHint;
import static org.lwjgl.opengl.GL11.GL_ALWAYS;
import static org.lwjgl.opengl.GL45.GL_LEQUAL;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
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_LESS;
import static org.lwjgl.opengl.GL11.GL_ONE;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_COLOR;
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_ZERO;
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.glDepthFunc;
import static org.lwjgl.opengl.GL11.glDepthMask;
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 static org.lwjgl.opengl.GL14.GL_FUNC_ADD;
import static org.lwjgl.opengl.GL14.glBlendEquation;
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniformMatrix4fv;
import static org.lwjgl.opengl.GL20.glUseProgram;
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.glClearBufferfv;
import static org.lwjgl.opengl.GL40.glBlendFunci;
import static org.lwjgl.system.MemoryUtil.NULL;

import java.nio.IntBuffer;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Quaterniond;
import org.joml.Quaternionf;
import org.joml.Sphered;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWWindowSizeCallbackI;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.debug.DebugRendering;
import electrosphere.renderer.framebuffer.Framebuffer;
import electrosphere.renderer.framebuffer.FramebufferUtils;
import electrosphere.renderer.framebuffer.Renderbuffer;
import electrosphere.renderer.light.LightManager;
import electrosphere.renderer.model.Model;
import electrosphere.renderer.pipelines.CompositePipeline;
import electrosphere.renderer.pipelines.FirstPersonItemsPipeline;
import electrosphere.renderer.pipelines.ImGuiPipeline;
import electrosphere.renderer.pipelines.MainContentNoOITPipeline;
import electrosphere.renderer.pipelines.MainContentPipeline;
import electrosphere.renderer.pipelines.NormalsForOutlinePipeline;
import electrosphere.renderer.pipelines.PostProcessingPipeline;
import electrosphere.renderer.pipelines.RenderScreenPipeline;
import electrosphere.renderer.pipelines.ShadowMapPipeline;
import electrosphere.renderer.pipelines.UIPipeline;
import electrosphere.renderer.pipelines.VolumeBufferPipeline;
import electrosphere.renderer.pipelines.debug.DebugContentPipeline;
import electrosphere.renderer.shader.ShaderProgram;
import electrosphere.renderer.texture.Texture;

public class RenderingEngine {
    
    
    
    public static final int GL_DEFAULT_FRAMEBUFFER = 0;
    public static final int GL_DEFAULT_RENDERBUFFER = 0;
    public static Texture screenTextureColor;
    public static Texture screenTextureDepth;
    public static Framebuffer screenFramebuffer;
    public static Renderbuffer screenRenderbuffer;
    public static int screenTextureVAO;
    public static ShaderProgram screenTextureShaders;
    public static ShaderProgram drawChannel;
    public static Framebuffer defaultFramebuffer;




    
    //the version of glsl to init imgui with
    private static String glslVersion = null;


    //shadow stuff
    public static Texture lightBufferDepthTexture;


    
    //depth framebuffer/shader for shadow mapping
    public static ShaderProgram lightDepthShaderProgram;
    public static Framebuffer lightDepthBuffer;

    //framebuffers for transparent textures
    public static float[] transparencyAccumulatorClear;
    public static Texture transparencyAccumulatorTexture;
    public static float[] transparencyRevealageClear;
    public static Texture transparencyRevealageTexture;
    public static Framebuffer transparencyBuffer;
    public static ShaderProgram oitCompositeProgram;

    /*
    render normals
    */
    public static Texture gameImageNormalsTexture;
    public static Framebuffer gameImageNormalsFramebuffer;
    public static ShaderProgram renderNormalsShader;

    /*
    Perspective volumetrics
    */
    public static Matrix4f nearVolumeProjectionMatrix = new Matrix4f();
    public static Matrix4f midVolumeProjectionMatrix = new Matrix4f();
    public static Matrix4f farVolumeProjectionMatrix = new Matrix4f();
    public static ShaderProgram volumeDepthShaderProgram;
    public static Framebuffer volumeDepthBackfaceFramebuffer;
    public static Texture volumeDepthBackfaceTexture;
    public static Framebuffer volumeDepthFrontfaceFramebuffer;
    public static Texture volumeDepthFrontfaceTexture;
    public static float volumeDepthLinearCoef = 0.1f;
    public static float volumeDepthQuadCoef = 0.01f;

    /*
    Necessary static variables for drawing
     */
    public static Matrix4d modelTransformMatrix = new Matrix4d();

    /*
    Vertical volumetrics
    TODO: implement
    */
    // static Texture volumeVerticalBackfaceTexture;
    // static Framebuffer volumeVerticalBackfaceBuffer;
    // static Texture volumeVerticalFrontfaceTexture;
    // static Framebuffer volumeVerticalFrontfaceBuffer;

    /*
    Post processing effects (ie kernels) textures, framebuffers, shaders
    */
    public static Texture normalsOutlineTexture;
    public static Framebuffer normalsOutlineFrambuffer;
    public static ShaderProgram normalsOutlineShader;

    /*
    compositing functions
    */
    public static ShaderProgram compositeAnimeOutline;

    
//    public static boolean renderHitboxes = false;
//    public static boolean renderPhysics = false;

    LightManager lightManager;
    
    public static int outputFramebuffer = 0;

    //used in calculating projection matrix
    static float aspectRatio = 1.0f;
    static float verticalFOV = 90.0f;

    //the current state of the rendering pipeline
    static RenderPipelineState renderPipelineState = new RenderPipelineState();

    //the opengl state
    static OpenGLState openGLState = new OpenGLState();

    //render pipelines
    MainContentPipeline mainContentPipeline = new MainContentPipeline();
    MainContentNoOITPipeline mainContentNoOITPipeline = new MainContentNoOITPipeline();
    DebugContentPipeline debugContentPipeline = new DebugContentPipeline();
    FirstPersonItemsPipeline firstPersonItemsPipeline = new FirstPersonItemsPipeline();
    ShadowMapPipeline shadowMapPipeline = new ShadowMapPipeline();
    VolumeBufferPipeline volumeBufferPipeline = new VolumeBufferPipeline();
    NormalsForOutlinePipeline normalsForOutlinePipeline = new NormalsForOutlinePipeline();
    PostProcessingPipeline postProcessingPipeline = new PostProcessingPipeline();
    CompositePipeline compositePipeline = new CompositePipeline();
    UIPipeline uiPipeline = new UIPipeline();
    RenderScreenPipeline renderScreenPipeline = new RenderScreenPipeline();
    ImGuiPipeline imGuiPipeline;
    
    
    public void createOpenglContext(){
        LoggerInterface.loggerRenderer.INFO("Create OpenGL Context");
        //Initializes opengl
        glfwInit();
        //Gives hints to glfw to control how opengl will be used
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glslVersion = "#version 430";
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        //headless option
        if(Globals.RUN_HIDDEN){
            glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
        }
        if(!Globals.WINDOW_DECORATED){
            glfwWindowHint(GLFW.GLFW_DECORATED, GLFW_FALSE);
        }
//        glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); Allows you to make the background transparent
//        glfwWindowHint(GLFW_OPACITY, 23);
        //Creates the window reference object
        if(Globals.userSettings.displayFullscreen() || Globals.WINDOW_FULLSCREEN){
            //below line is for fullscreen
            Globals.window = glfwCreateWindow(Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, "ORPG", glfwGetPrimaryMonitor(), NULL);
        } else {
            Globals.window = glfwCreateWindow(Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, "ORPG", NULL, NULL);
        }
        // Errors for failure to create window (IE: No GUI mode on linux ?)
        if (Globals.window == NULL) {
            LoggerInterface.loggerEngine.ERROR("Failed to make window.", new Exception("Renderer Creation Failure"));
            glfwTerminate();
        }
        //set resize callback
        GLFWWindowSizeCallbackI windowSizeCallback = new GLFWWindowSizeCallbackI(){
            @Override
            public void invoke(long window, int width, int height){
                Globals.WINDOW_HEIGHT = height;
                Globals.WINDOW_WIDTH = width;
            }
        };
        GLFW.glfwSetWindowSizeCallback(Globals.window, windowSizeCallback);
        //Makes the window that was just created the current OS-level window context
        glfwMakeContextCurrent(Globals.window);
        //Maximize it
        glfwMaximizeWindow(Globals.window);
        //grab actual framebuffer 
        IntBuffer xBuffer = BufferUtils.createIntBuffer(1);
        IntBuffer yBuffer = BufferUtils.createIntBuffer(1);
        GLFW.glfwGetFramebufferSize(Globals.window, xBuffer, yBuffer);
        
        int bufferWidth = xBuffer.get();
        int bufferHeight = yBuffer.get();

        //get title bar size
        Globals.WINDOW_TITLE_BAR_HEIGHT = Globals.WINDOW_HEIGHT - bufferHeight;
        
//        System.out.println(Globals.WINDOW_TITLE_BAR_HEIGHT);
        
        Globals.WINDOW_WIDTH = bufferWidth;
        Globals.WINDOW_HEIGHT = bufferHeight;

        //
        // Attach controls callbacks
        //
        //set key callback
        GLFW.glfwSetKeyCallback(Globals.window, Globals.controlCallback);
        GLFW.glfwSetMouseButtonCallback(Globals.window, Globals.mouseCallback);
        GLFW.glfwSetScrollCallback(Globals.window, Globals.scrollCallback);

        //get title bar dimensions
//        setTitleBarDimensions();
        
        //Creates the OpenGL capabilities for the program.
        GL.createCapabilities();

        //get environment constraints
        openGLState.storeCurrentEnvironmentContraints();

        //init imgui pipeline
        imGuiPipeline = new ImGuiPipeline(Globals.window, glslVersion);

        //This enables Z-buffering so that farther-back polygons are not drawn over nearer ones
        glEnable(GL_DEPTH_TEST);
        
        // Support for transparency
        openGLState.glBlend(true);
        openGLState.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        //this disables vsync to make game run faster
        //https://stackoverflow.com/questions/55598376/glfwswapbuffers-is-slow
        if(!Globals.userSettings.graphicsPerformanceEnableVSync()){
            GLFW.glfwSwapInterval(0);
        }
        
//        //Hide the cursor and capture it
//        glfwSetInputMode(Globals.window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
        
        
        
        //init screen rendering quadrant
        screenTextureVAO = createScreenTextureVAO();
//        initScreenTextureShaderProgram();
        screenTextureShaders = ShaderProgram.loadSpecificShader("/Shaders/screentexture/simple1/simple1.vs", "/Shaders/screentexture/simple1/simple1.fs");
//        screenTextureShaders = ShaderProgram.loadSpecificShader("/Shaders/screentexture/drawDepthBuffer/drawDepthBuffer.vs", "/Shaders/screentexture/drawDepthBuffer/drawDepthBuffer.fs");

        //default framebuffer
        defaultFramebuffer = new Framebuffer(0);
        
        //generate framebuffers
        screenTextureColor = FramebufferUtils.generateScreenTextureColor(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        screenTextureDepth = FramebufferUtils.generateScreenTextureDepth(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        screenFramebuffer = FramebufferUtils.generateScreenTextureFramebuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), screenTextureColor, screenTextureDepth);
        openGLState.glBindFramebuffer(GL_FRAMEBUFFER, GL_DEFAULT_FRAMEBUFFER);
        glBindRenderbuffer(GL_RENDERBUFFER, GL_DEFAULT_RENDERBUFFER);

        //
        //Channel debug program
        //
        drawChannel = ShaderProgram.loadSpecificShader("/Shaders/screentexture/drawChannel/drawChannel.vs", "/Shaders/screentexture/drawChannel/drawChannel.fs");

        //
        //create light depth framebuffer/shader for shadowmapping
        //
        lightDepthShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/lightDepth/lightDepth.vs", "/Shaders/lightDepth/lightDepth.fs");
        Globals.depthMapShaderProgramLoc = lightDepthShaderProgram.getShaderId();
        lightDepthBuffer = FramebufferUtils.generateDepthBuffer(openGLState);
        lightBufferDepthTexture = lightDepthBuffer.getDepthTexture();
//        glEnable(GL_CULL_FACE); // enabled for shadow mapping

        //
        //create volume depth framebuffer/shader for volumetric rendering
        //
        volumeDepthShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/volumeBuffer/volumetric.vs", "/Shaders/volumeBuffer/volumetric.fs");
        volumeDepthBackfaceTexture = FramebufferUtils.generateDepthBufferTexture(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        volumeDepthBackfaceFramebuffer = FramebufferUtils.generateDepthBuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), volumeDepthBackfaceTexture);
        volumeDepthFrontfaceTexture = FramebufferUtils.generateDepthBufferTexture(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        volumeDepthFrontfaceFramebuffer = FramebufferUtils.generateDepthBuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), volumeDepthFrontfaceTexture);

        //
        //Game normals
        //
        /*
        gameImageNormalsTexture;
    static Framebuffer gameImageNormalsFramebuffer;
    static ShaderProgram renderNormalsShader;
        */
        gameImageNormalsTexture = FramebufferUtils.generateScreenTextureColor(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        Texture gameImageNormalsDepthTexture = FramebufferUtils.generateScreenTextureDepth(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        gameImageNormalsFramebuffer = FramebufferUtils.generateScreenTextureFramebuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), gameImageNormalsTexture, gameImageNormalsDepthTexture);
        renderNormalsShader = ShaderProgram.loadSpecificShader("Shaders/anime/renderNormals.vs", "Shaders/anime/renderNormals.fs");

        //
        //Transparency framebuffers
        //
        transparencyAccumulatorClear = new float[]{0.0f, 0.0f, 0.0f, 0.0f};
        transparencyAccumulatorTexture = FramebufferUtils.generateOITAccumulatorTexture(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        transparencyRevealageClear = new float[]{1.0f, 1.0f, 1.0f, 1.0f};
        transparencyRevealageTexture = FramebufferUtils.generateOITRevealageTexture(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        transparencyBuffer = FramebufferUtils.generateOITFramebuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), transparencyAccumulatorTexture, transparencyRevealageTexture, screenTextureDepth);
        oitCompositeProgram = ShaderProgram.loadSpecificShader("Shaders/oit/composite.vs", "Shaders/oit/composite.fs");

        //projection matrices
        nearVolumeProjectionMatrix.setPerspective((float)(Globals.verticalFOV * Math.PI /180.0f), (float)Globals.WINDOW_WIDTH / (float)Globals.WINDOW_HEIGHT, 0.1f, 100);

        //
        //Postprocessing textures and buffers
        //
        /*
        static Texture normalsOutlineTexture;
    static Framebuffer normalsOutlineFrambuffer;
    static ShaderProgram normalsOutlineShader;
        */
        normalsOutlineTexture = FramebufferUtils.generateScreenTextureColorAlpha(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY());
        normalsOutlineFrambuffer = FramebufferUtils.generateScreenTextureFramebuffer(openGLState, Globals.userSettings.getRenderResolutionX(), Globals.userSettings.getRenderResolutionY(), normalsOutlineTexture);
        // normalsOutlineShader = ShaderProgram.loadSpecificShader("Shaders/anime/outlineNormals.vs", "Shaders/anime/outlineNormals.fs");
        Globals.assetManager.addShaderToQueue("Shaders/anime/outlineNormals.vs", "Shaders/anime/outlineNormals.fs");

        //
        //Compositing shaders
        //
        compositeAnimeOutline = ShaderProgram.loadSpecificShader("Shaders/anime/compositeAnimeOutline.vs", "Shaders/anime/compositeAnimeOutline.fs");


        //instantiate light manager
        lightManager = new LightManager();
        
        //
        //Fog
        //
        //enable fog
//        glEnable(GL_FOG);
//        //set the equation to use for fog
//        glFogf(GL_FOG_MODE,GL_LINEAR);
////        glFogf(GL_FOG_MODE,GL_EXP2);
//        //set the density of the fog
//        glFogf(GL_FOG_DENSITY,1.0f);
//        //these are applicable for the linear equation
//        glFogf(GL_FOG_START,0.8f);
//        glFogf(GL_FOG_END,1.0f);
//        //fog color
//        FloatBuffer fogColor = FloatBuffer.allocate(4);
//        fogColor.put(1.0f);
//        fogColor.put(1.0f);
//        fogColor.put(1.0f);
//        fogColor.put(1.0f);
//        fogColor.flip();
//        GL11.glFogfv(GL_FOG_COLOR, fogColor);

        //
        //Init pipelines
        //
        mainContentPipeline.setFirstPersonPipeline(firstPersonItemsPipeline);
        
        //
        // Projection and View matrix creation
        //
        Globals.projectionMatrix = new Matrix4f();
        Globals.viewMatrix = new Matrix4f();
        verticalFOV = (float)(Globals.verticalFOV * Math.PI /180.0f);
        //set local aspect ratio and global aspect ratio at the same time
        aspectRatio = Globals.aspectRatio = Globals.WINDOW_WIDTH / (float)Globals.WINDOW_HEIGHT;
        float nearClip = 0.001f;
        Globals.projectionMatrix.setPerspective(verticalFOV, Globals.aspectRatio, nearClip, Globals.userSettings.getGraphicsViewDistance());
        Globals.viewMatrix.translation(new Vector3f(0.0f,0.0f,-3.0f));
    }
    
    
    /**
     * Main function to draw the screen
     */
    public void drawScreen(){
        
        //calculate render angle for frustum culling
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
            updateFrustumBox();
        }
        
        //generate depth map
        if(Globals.RENDER_FLAG_RENDER_SHADOW_MAP){
            shadowMapPipeline.render(openGLState, renderPipelineState);
        }

        //render volume buffer
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
            volumeBufferPipeline.render(openGLState, renderPipelineState);
        }
        
        //Update light buffer
        lightManager.updateData();
        checkError();

        
        //Render content to the game framebuffer
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
            if(Globals.userSettings.getGraphicsPerformanceOIT()){
                mainContentPipeline.render(openGLState, renderPipelineState);
            } else {
                mainContentNoOITPipeline.render(openGLState, renderPipelineState);
            }
            checkError();
            debugContentPipeline.render(openGLState, renderPipelineState);
            checkError();
            normalsForOutlinePipeline.render(openGLState, renderPipelineState);
            checkError();
            firstPersonItemsPipeline.render(openGLState, renderPipelineState);
            checkError();
            postProcessingPipeline.render(openGLState, renderPipelineState);
            checkError();
            compositePipeline.render(openGLState, renderPipelineState);
            checkError();
        }
        
        
        

        //bind default FBO
        openGLState.glBindFramebuffer(GL_FRAMEBUFFER,0);
        
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
        glClear(GL_COLOR_BUFFER_BIT);
        
        //Render the game framebuffer texture to a quad
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER){
            renderScreenPipeline.render(openGLState, renderPipelineState);
            checkError();
        }
        
        //render ui
        uiPipeline.render(openGLState, renderPipelineState);
        checkError();
        
        //Render boundaries of ui elements
        if(Globals.RENDER_FLAG_RENDER_UI_BOUNDS){
            DebugRendering.drawUIBoundsWireframe();
        }

        /**
         * Render imgui
         */
        imGuiPipeline.render(openGLState, renderPipelineState);
        checkError();
        
        
        //check for errors
        // checkError();
        
        //check and call events and swap the buffers
        LoggerInterface.loggerRenderer.DEBUG_LOOP("GLFW Swap buffers");
        glfwSwapBuffers(Globals.window);
        LoggerInterface.loggerRenderer.DEBUG_LOOP("GLFW Poll Events");
        glfwPollEvents();
        LoggerInterface.loggerRenderer.DEBUG_LOOP("Check OpenGL Errors");
        checkError();
    }

    /**
     * Updates the frustum box of the render pipeline
     */
    void updateFrustumBox(){
        renderPipelineState.updateFrustumIntersection(Globals.projectionMatrix, Globals.viewMatrix);
    }
    
    public void bindFramebuffer(int framebufferPointer){
        openGLState.glBindFramebuffer(GL_FRAMEBUFFER, framebufferPointer);
    }
    
    public void setTitleBarDimensions(){
        IntBuffer tLeft = BufferUtils.createIntBuffer(1);
        IntBuffer tTop = BufferUtils.createIntBuffer(1);
        IntBuffer tRight = BufferUtils.createIntBuffer(1);
        IntBuffer tBottom = BufferUtils.createIntBuffer(1);

        // Get the title bar dims
        GLFW.glfwGetWindowFrameSize(Globals.window, tLeft, tTop, tRight, tBottom);
        Globals.WINDOW_TITLE_BAR_HEIGHT = tTop.get();
//        System.out.println(tLeft.get() + " " + tTop.get() + " " + tRight.get() + " " + tBottom.get());
    }

    public Texture getVolumeBackfaceTexture(){
        return volumeDepthBackfaceTexture;
    }

    public Texture getVolumeFrontfaceTexture(){
        return volumeDepthFrontfaceTexture;
    }

    public LightManager getLightManager(){
        return lightManager;
    }

    public static void incrementOutputFramebuffer(){
        outputFramebuffer++;
        if(outputFramebuffer > 8){
            outputFramebuffer = 0;
        }
    }

    public static void setFOV(float verticalFOV){
        RenderingEngine.verticalFOV = verticalFOV;
        calculateProjectionMatrix();
    }

    public static void setAspectRatio(float aspectRatio){
        RenderingEngine.aspectRatio = aspectRatio;
        calculateProjectionMatrix();
    }

    static void calculateProjectionMatrix(){
        float radVerticalFOV = (float)(RenderingEngine.verticalFOV * Math.PI /180.0f);
        float nearClip = 0.001f;
        Globals.projectionMatrix.setPerspective(radVerticalFOV, RenderingEngine.aspectRatio, nearClip, Globals.userSettings.getGraphicsViewDistance());
    }

    /**
     * Gets the imgui pipeline
     * @return The imgui pipeline
     */
    public ImGuiPipeline getImGuiPipeline(){
        return this.imGuiPipeline;
    }

    /**
     * Gets the debug content pipeline
     * @return The debug content pipeline
     */
    public DebugContentPipeline getDebugContentPipeline(){
        return this.debugContentPipeline;
    }

    /**
     * Gets the current render pipeline state
     * @return The current render pipeline state
     */
    public RenderPipelineState getRenderPipelineState(){
        return renderPipelineState;
    }

    /**
     * Gets the current opengl state
     * @return
     */
    public OpenGLState getOpenGLState(){
        return openGLState;
    }

    /**
     * Tries to recapture the screen
     */
    public static void recaptureIfNecessary(){
        if(Globals.controlHandler.shouldRecapture()){
            //Makes the window that was just created the current OS-level window context
            glfwMakeContextCurrent(Globals.window);
            // //Maximize it
            glfwMaximizeWindow(Globals.window);
            //grab focus
            GLFW.glfwFocusWindow(Globals.window);
            //apply mouse controls state
            if(Globals.controlHandler.isMouseVisible()){
                Globals.controlHandler.showMouse();
            } else {
                Globals.controlHandler.hideMouse();
            }
            Globals.controlHandler.setRecapture(false);
        }
    }

    /**
     * Checks for any errors currently caught by OpenGL.
     * Refer: https://docs.gl/gl4/glGetError
     */
    public boolean checkError(){
        int error = this.getError();
        if(error != GL11.GL_NO_ERROR){
            LoggerInterface.loggerRenderer.ERROR("checkError - " + getErrorInEnglish(error), new IllegalStateException("OpenGL Error"));
            return true;
        }
        return false;
    }

    /**
     * Gets the current error code
     * @return The error code
     */
    public int getError(){
        int lastCode = GL11.glGetError();
        int currentCode = lastCode;
        while((currentCode = GL11.glGetError()) != GL11.GL_NO_ERROR){
            lastCode = currentCode;
        }
        return lastCode;
    }

    /**
     * Checks for any errors currently caught by OpenGL.
     * Refer: https://docs.gl/gl4/glGetError
     * @param errorCode The error code
     * @return The message
     */
    public static String getErrorInEnglish(int errorCode){
        switch(errorCode){
            case GL11.GL_NO_ERROR: {
                return null;
            }
            case GL11.GL_INVALID_ENUM: {
                return "An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.";
            }
            case GL11.GL_INVALID_VALUE: {
                return "A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.";
            }
            case GL11.GL_INVALID_OPERATION: {
                return "The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.";
            }
            case GL30.GL_INVALID_FRAMEBUFFER_OPERATION: {
                return "The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag. ";
            }
            case GL11.GL_OUT_OF_MEMORY: {
                return "There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded. ";
            }
            case GL11.GL_STACK_UNDERFLOW: {
                return "An attempt has been made to perform an operation that would cause an internal stack to underflow. ";
            }
            case GL11.GL_STACK_OVERFLOW: {
                return "An attempt has been made to perform an operation that would cause an internal stack to overflow. ";
            }
            default: {
                return "Un-enum'd error or no error. Code: " + errorCode;
            }
        }
    }
    
}
