package electrosphere.renderer;

import electrosphere.entity.CameraEntityUtils;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.hitbox.HitboxData;
import electrosphere.entity.types.hitbox.HitboxUtils;
import electrosphere.game.data.creature.type.CollidableTemplate;
import electrosphere.game.server.pathfinding.navmesh.NavCube;
import electrosphere.game.server.pathfinding.navmesh.NavMesh;
import electrosphere.game.server.pathfinding.navmesh.NavShape;
import electrosphere.logger.LoggerInterface;
import electrosphere.main.Globals;
import static electrosphere.main.Main.deltaTime;
import static electrosphere.main.Main.view_Range;
import static electrosphere.renderer.RenderUtils.createScreenTextureVAO;
import electrosphere.renderer.framebuffer.Framebuffer;
import electrosphere.renderer.framebuffer.FramebufferUtils;
import electrosphere.renderer.framebuffer.Renderbuffer;
import electrosphere.renderer.texture.Texture;
import electrosphere.renderer.ui.Widget;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.joml.Matrix4f;
import org.joml.Quaternionf;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
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_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.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.glfw.GLFWWindowSizeCallbackI;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
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_DEPTH_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_EXP2;
import static org.lwjgl.opengl.GL11.GL_FOG;
import static org.lwjgl.opengl.GL11.GL_FOG_COLOR;
import static org.lwjgl.opengl.GL11.GL_FOG_DENSITY;
import static org.lwjgl.opengl.GL11.GL_FOG_END;
import static org.lwjgl.opengl.GL11.GL_FOG_MODE;
import static org.lwjgl.opengl.GL11.GL_FOG_START;
import static org.lwjgl.opengl.GL11.GL_LINEAR;
import static org.lwjgl.opengl.GL11.GL_NEAREST;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
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.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.glDisable;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glFogf;
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.GL20.glGetUniformLocation;
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.glBlitFramebuffer;
import static org.lwjgl.system.MemoryUtil.NULL;

public class RenderingEngine {
    
    
    
    public static final int GL_DEFAULT_FRAMEBUFFER = 0;
    public static final int GL_DEFAULT_RENDERBUFFER = 0;
    static Framebuffer screenFramebuffer;
    static Renderbuffer screenRenderbuffer;
    static int screenTextureVAO;
    static ShaderProgram screenTextureShaders;
    
    static ShaderProgram lightDepthShaderProgram;
    static Framebuffer lightDepthBuffer;
    
//    public static boolean renderHitboxes = false;
//    public static boolean renderPhysics = false;
    
    static float currentViewPlanarAngle;
    
    ShaderProgram activeProgram;
    
    
    public void createOpenglContext(){
        //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, 1);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//        glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE); Allows you to make the background transparent
//        glfwWindowHint(GLFW_OPACITY, 23);
        //Creates the window reference object
        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
//        GLFW.glfwSetWindowSizeCallback​(Globals.window, new GLFWWindowSizeCallbackI(){
//            @Override
//            public void invoke(long window, int width, int height){
//                Globals.WINDOW_HEIGHT = height;
//                Globals.WINDOW_WIDTH = width;
//            }
//        });
        //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;
        
        //get title bar dimensions
//        setTitleBarDimensions();
        
        //Creates the OpenGL capabilities for the program.
        GL.createCapabilities();

        //This enables Z-buffering so that farther-back polygons are not drawn over nearer ones
        glEnable(GL_DEPTH_TEST);
        
        // Support for transparency
        glEnable(GL_BLEND);
        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");
        
        //generate framebuffers
        screenFramebuffer = FramebufferUtils.generateScreensizeTextureFramebuffer();
        glBindFramebuffer(GL_FRAMEBUFFER, GL_DEFAULT_FRAMEBUFFER);
        glBindRenderbuffer(GL_RENDERBUFFER, GL_DEFAULT_RENDERBUFFER);
        
        lightDepthShaderProgram = ShaderProgram.loadSpecificShader("/Shaders/lightDepth/lightDepth.vs", "/Shaders/lightDepth/lightDepth.fs");
        Globals.depthMapShaderProgramLoc = lightDepthShaderProgram.shaderProgram;
        lightDepthBuffer = FramebufferUtils.generateDepthBuffer();
        Globals.shadowMapTextureLoc = lightDepthBuffer.getTexturePointer();
//        glEnable(GL_CULL_FACE); // enabled for shadow mapping
        
        //
        //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);
        
        //
        // Projection and View matrix creation
        //
        Globals.projectionMatrix = new Matrix4f();
        Globals.viewMatrix = new Matrix4f();
        float FOV = (float)(Globals.FOV * Math.PI /180.0f);
        Globals.projectionMatrix.setPerspective(FOV, 1.0f, 0.1f, view_Range);
        Globals.viewMatrix.translation(new Vector3f(0.0f,0.0f,-3.0f));
    }
    
    static float calculateAngle(Vector3f origin, Vector3f target){
        float rVal = (float)Math.atan2(target.z - origin.z, target.x - origin.x);
        if(rVal < 0){
            rVal = rVal + (float)(Math.PI * 2);
        }
        return rVal;
    }
    
    static float calculateDist(Vector3f origin, Vector3f target){
        return origin.distance(target);
    }
    
    
    static boolean drawPoint(Vector3f cameraPos, Vector3f position){
        boolean rVal = true;
        float phi = (float)Math.abs((calculateAngle(cameraPos,position) - currentViewPlanarAngle) % (Math.PI * 2.0f));
        if(phi > Math.PI){
            phi = (float)(Math.PI * 2) - phi;
        }
        float rotationalDiff = phi;
        float dist = calculateDist(new Vector3f(cameraPos.x,0,cameraPos.z), new Vector3f(position.x,0,position.z));
        if(rotationalDiff > (Globals.FOV / 180 * Math.PI) && dist > 300){
            rVal = false;
        }
        return rVal;
    }
    
    void calculateRenderingAngle(){
        currentViewPlanarAngle = calculateAngle(CameraEntityUtils.getCameraEye(Globals.playerCamera), new Vector3f(0,0,0));
//        System.out.println(currentViewPlanarAngle);
    }
    
    
    public void drawScreen(){
        
        //calculate render angle for frustum culling
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
            calculateRenderingAngle();
        }
        
        //
        //first pass: generate depth map
        //
        if(Globals.RENDER_FLAG_RENDER_SHADOW_MAP){
            renderShadowMapContent();
        }
        
        
        /*
        Render content to the game framebuffer
        */
        if(Globals.RENDER_FLAG_RENDER_SCREEN_FRAMEBUFFER_CONTENT){
            renderGameContent();
        }
        
        
        

        //bind default FBO
        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){
            renderScreenFramebuffer();
        }
        
        /*
        Render black background
        */
        if(Globals.RENDER_FLAG_RENDER_BLACK_BACKGROUND){
            renderBlackBackground();
        }
        
        /*
        Render white background
        */
        if(Globals.RENDER_FLAG_RENDER_WHITE_BACKGROUND){
            renderWhiteBackground();
        }
        
        /*
        Render any ui elements
        */
        if(Globals.RENDER_FLAG_RENDER_UI){
            renderUI();
        }
        
        
        
        
        
        
        //check and call events and swap the buffers
        glfwSwapBuffers(Globals.window);
        glfwPollEvents();
    }
    
    
    static void renderShadowMapContent(){
        
        Matrix4f modelTransformMatrix = new Matrix4f();
        
        //set the viewport to shadow map size
        glViewport(0, 0, 4096, 4096);
        glEnable(GL_DEPTH_TEST);
        
        Globals.renderingEngine.setActiveShader(lightDepthShaderProgram);
        
        lightDepthBuffer.bind();
        glClear(GL_DEPTH_BUFFER_BIT);
        glActiveTexture(GL_TEXTURE0);
//            glBindTexture(GL_TEXTURE_2D, woodTexture);
//            renderScene(simpleDepthShader);

        float eyeX = -1.0f;
        float eyeY = 10.0f;
        float eyeZ = -5.5f;
        float nearPlane = 0.01f;
        float eyeDist = (float)Math.sqrt(eyeX * eyeX + eyeY * eyeY + eyeZ * eyeZ);
        float farPlane = eyeDist + 10.0f;
        float sidesMagnitude = (float)Math.sqrt(eyeDist);
        //set matrices for light render
        Matrix4f lightProjection = new Matrix4f().setOrtho(-sidesMagnitude, sidesMagnitude, -sidesMagnitude, sidesMagnitude, nearPlane, farPlane);//glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane); 
        Matrix4f lightView = new Matrix4f().setLookAt(
                new Vector3f(eyeX, eyeY, eyeZ),
                new Vector3f( 0.0f, 0.0f,  0.0f),
                new Vector3f( 0.0f, 1.0f,  0.0f)
        );
        Globals.lightDepthMatrix = new Matrix4f(lightProjection).mul(lightView);

        glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "lightSpaceMatrix"), false, Globals.lightDepthMatrix.get(new float[16]));

//        glCullFace(GL_FRONT);

        //
        //     D R A W     A L L     E N T I T I E S
        //
        Vector3f cameraPos = CameraEntityUtils.getCameraCenter(Globals.playerCamera);
        modelTransformMatrix = new Matrix4f();
        for(Entity currentEntity : Globals.entityManager.getDrawable()){
            Vector3d position = EntityUtils.getPosition(currentEntity);
            if(
                    (boolean)currentEntity.getData(EntityDataStrings.DATA_STRING_DRAW) && 
                    drawPoint(cameraPos,new Vector3f((float)position.x,(float)position.y,(float)position.z)) &&
                    currentEntity.getDataKeys().contains(EntityDataStrings.DRAW_CAST_SHADOW)
                    ){
                //fetch actor
                Actor currentActor = EntityUtils.getActor(currentEntity);
                //calculate camera-modified vector3f
                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                //calculate and apply model transform
                modelTransformMatrix = modelTransformMatrix.identity();
                modelTransformMatrix.translate(cameraModifiedPosition);
                modelTransformMatrix.rotate(EntityUtils.getRotation(currentEntity));
                modelTransformMatrix.scale(EntityUtils.getScale(currentEntity));
                currentActor.applyModelMatrix(modelTransformMatrix);
                //draw
//                if(!currentEntity.getDataKeys().contains(EntityDataStrings.TERRAIN_IS_TERRAIN) && !currentEntity.getDataKeys().contains(EntityDataStrings.DATA_STRING_CREATURE_IS_CREATURE)){
                currentActor.drawForDepthBuffer();
//                    System.out.println(currentActor.modelPath);
//                }
            }
        }

        
        //reset texture
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, 0);
        //bind default framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        //reset the viewport to screen size
        glViewport(0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
        //resume culling backface
//        glCullFace(GL_BACK);
    }
    
    
    static void renderGameContent(){
        
        Matrix4f modelTransformMatrix = new Matrix4f();
        
        //bind screen fbo
        screenFramebuffer.bind();
        glEnable(GL_DEPTH_TEST);
        glViewport(0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
        
        ///
        ///     R E N D E R I N G      S T U F F
        ///
        //Sets the background color.
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        //
        //     D R A W     A L L     E N T I T I E S
        //
        Vector3f cameraPos = CameraEntityUtils.getCameraCenter(Globals.playerCamera);
        modelTransformMatrix = new Matrix4f();
        for(Entity currentEntity : Globals.entityManager.getDrawable()){
            Vector3d position = EntityUtils.getPosition(currentEntity);
            if((boolean)currentEntity.getData(EntityDataStrings.DATA_STRING_DRAW) && drawPoint(cameraPos,new Vector3f((float)position.x,(float)position.y,(float)position.z))){
                //fetch actor
                Actor currentActor = EntityUtils.getActor(currentEntity);
                currentActor.incrementAnimationTime(0.001);
                //increment animations
                //this is incremented in the shadow calculations
    //            if(currentActor.getCurrentAnimation() != null){
    //                currentActor.incrementAnimationTime(deltaTime * 500);
    //            }
                //calculate camera-modified vector3f
                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                //calculate and apply model transform
                modelTransformMatrix.identity();
                modelTransformMatrix.translate(cameraModifiedPosition);
                modelTransformMatrix.rotate(EntityUtils.getRotation(currentEntity));
                modelTransformMatrix.scale(EntityUtils.getScale(currentEntity));
                currentActor.applyModelMatrix(modelTransformMatrix);
                //draw
                currentActor.draw();

    //                Model currentModel = Globals.assetManager.fetchModel(EntityUtils.getEntityModelPath(currentEntity));
    //                if(currentModel != null){
    //                    if(currentModel.currentAnimation != null){
    //                        currentModel.incrementTime(deltaTime * 500);
    //                    }
    //                    currentModel.modelMatrix = new Matrix4f();
    //                    currentModel.modelMatrix.translate(new Vector3f(EntityUtils.getEntityPosition(currentEntity)).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera)).sub(new Vector3f(0,1,0)));
    //                    currentModel.modelMatrix.rotate(EntityUtils.getEntityRotation(currentEntity));
    //                    currentModel.modelMatrix.scale(EntityUtils.getEntityScale(currentEntity));
    //                    currentModel.draw();
    //                }
            }
        }
        
        if(Globals.userSettings.graphicsDebugDrawCollisionSpheres()){
            for(Entity currentHitbox : Globals.hitboxManager.getAllHitboxes()){
                if((boolean)currentHitbox.getData(EntityDataStrings.DATA_STRING_DRAW)){
                    Model hitboxModel;
                    HitboxData data = HitboxUtils.getHitboxData(currentHitbox);
                    if(data.isActive()){
                        if(data.getType().equals(EntityDataStrings.COLLISION_ENTITY_DATA_TYPE_HURT)){
                            if((hitboxModel = Globals.assetManager.fetchModel("Models/unitsphere.fbx")) != null){
                                Vector3d position = EntityUtils.getPosition(currentHitbox);
                                //calculate camera-modified vector3f
                                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
                //                modelTransformMatrix.translate(-0.25f, 0.0f, 0.5f); //center sphere
                                modelTransformMatrix.scale(data.getRadius() * 2);
                                hitboxModel.modelMatrix = modelTransformMatrix;
                                hitboxModel.draw(true, true, false, true, true, true, true);
                            }
                        } else if(data.getType().equals(EntityDataStrings.COLLISION_ENTITY_DATA_TYPE_HIT)){
                            if((hitboxModel = Globals.assetManager.fetchModel("Models/unitsphere_1.fbx")) != null){
                                Vector3d position = EntityUtils.getPosition(currentHitbox);
                                //calculate camera-modified vector3f
                                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
                //                modelTransformMatrix.translate(-0.25f, 0.0f, 0.5f); //center sphere
                                modelTransformMatrix.scale(data.getRadius() * 2);
                                hitboxModel.modelMatrix = modelTransformMatrix;
                                hitboxModel.draw(true, true, false, true, true, true, true);
                            }
                        }
                    } else {
                        if((hitboxModel = Globals.assetManager.fetchModel("Models/unitsphere_grey.fbx")) != null){
                            Vector3d position = EntityUtils.getPosition(currentHitbox);
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera)));
            //                modelTransformMatrix.translate(-0.25f, 0.0f, 0.5f); //center sphere
                            modelTransformMatrix.scale(data.getRadius() * 2);
                            hitboxModel.modelMatrix = modelTransformMatrix;
                            hitboxModel.draw(true, true, false, true, true, true, true);
                        }
                    }
                }
            }
        }
        
        if(Globals.userSettings.graphicsDebugDrawPhysicsObjects()){
            Model physicsGraphicsModel;
            for(Entity physicsEntity : Globals.collisionEngine.getDynamicPhysicsEntities()){
                if((boolean)physicsEntity.getData(EntityDataStrings.DATA_STRING_DRAW)){
                    CollidableTemplate template = (CollidableTemplate)physicsEntity.getData(EntityDataStrings.PHYSICS_MODEL_TEMPLATE);
                    switch(template.getType()){
                        case "CYLINDER":
                            if((physicsGraphicsModel = Globals.assetManager.fetchModel("Models/unitcylinder.fbx")) != null){
                                Vector3d position = EntityUtils.getPosition(physicsEntity);
                                //calculate camera-modified vector3f
                                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).add(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
    //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                                modelTransformMatrix.scale(template.getDimension1(),template.getDimension2(),template.getDimension3());
                                physicsGraphicsModel.modelMatrix = modelTransformMatrix;
                                physicsGraphicsModel.draw(true, true, false, true, true, true, true);
                            }
                            break;
                        case "CUBE":
                            if((physicsGraphicsModel = Globals.assetManager.fetchModel("Models/unitcube.fbx")) != null){
                                Vector3d position = EntityUtils.getPosition(physicsEntity);
                                Vector3f scale = EntityUtils.getScale(physicsEntity);
                                Quaternionf rotation = EntityUtils.getRotation(physicsEntity);
                                //calculate camera-modified vector3f
                                Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                                modelTransformMatrix.identity();
                                modelTransformMatrix.translate(cameraModifiedPosition);
                                modelTransformMatrix.rotate(rotation);
        //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                                modelTransformMatrix.scale(template.getDimension1(),template.getDimension2(),template.getDimension3());
                                physicsGraphicsModel.modelMatrix = modelTransformMatrix;
                                physicsGraphicsModel.draw(true, true, false, true, true, true, true);
                            }
                            break;
                    }
                }
            }
            for(Entity physicsEntity : Globals.collisionEngine.getStructurePhysicsEntities()){
                if((boolean)physicsEntity.getData(EntityDataStrings.DATA_STRING_DRAW)){
                    if(physicsEntity.getDataKeys().contains(EntityDataStrings.COLLISION_ENTITY_TYPE_PLANE)){
                        if((physicsGraphicsModel = Globals.assetManager.fetchModel("Models/unitplane.fbx")) != null){
                            Vector3d position = EntityUtils.getPosition(physicsEntity);
                            Vector3f scale = EntityUtils.getScale(physicsEntity);
                            Quaternionf rotation = EntityUtils.getRotation(physicsEntity);
                            //calculate camera-modified vector3f
                            Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.rotate(rotation);
    //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                            modelTransformMatrix.scale(scale);
                            physicsGraphicsModel.modelMatrix = modelTransformMatrix;
                            physicsGraphicsModel.draw(true, true, false, true, true, true, true);
                        }
                    } else if(physicsEntity.getDataKeys().contains(EntityDataStrings.COLLISION_ENTITY_TYPE_CUBE)){
                        if((physicsGraphicsModel = Globals.assetManager.fetchModel("Models/unitcube.fbx")) != null){
                            Vector3d position = EntityUtils.getPosition(physicsEntity);
                            Vector3f scale = EntityUtils.getScale(physicsEntity);
                            Quaternionf rotation = EntityUtils.getRotation(physicsEntity);
                            //calculate camera-modified vector3f
                            Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.rotate(rotation);
    //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                            modelTransformMatrix.scale(scale);
                            physicsGraphicsModel.modelMatrix = modelTransformMatrix;
                            physicsGraphicsModel.draw(true, true, false, true, true, true, true);
                        }
                    }
                }
            }
        }
        
        if(Globals.userSettings.graphicsDebugDrawNavmesh()){
            Model shapeGraphicsModel;
            for(NavMesh mesh : Globals.navMeshManager.getMeshes()){
                for(NavShape shape : mesh.getNodes()){
                    if(shape instanceof NavCube){
                        if((shapeGraphicsModel = Globals.assetManager.fetchModel("Models/unitcube.fbx")) != null){
                            NavCube cube = (NavCube)shape;
                            Vector3d position = new Vector3d(cube.getMinPoint()).add(cube.getMaxPoint()).mul(0.5);
                            Vector3f scale = new Vector3f((float)(cube.getMaxPoint().x-cube.getMinPoint().x)/2,(float)(cube.getMaxPoint().y-cube.getMinPoint().y)/2,(float)(cube.getMaxPoint().z-cube.getMinPoint().z)/2);
                            Quaternionf rotation = new Quaternionf();
                            //calculate camera-modified vector3f
                            Vector3f cameraModifiedPosition = new Vector3f((float)position.x,(float)position.y,(float)position.z).sub(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
                            modelTransformMatrix.identity();
                            modelTransformMatrix.translate(cameraModifiedPosition);
                            modelTransformMatrix.rotate(rotation);
    //                            modelTransformMatrix.translate(template.getOffsetX(),template.getOffsetY(),template.getOffsetZ()); //center sphere
                            modelTransformMatrix.scale(scale);
                            shapeGraphicsModel.modelMatrix = modelTransformMatrix;
                            shapeGraphicsModel.draw(true, true, false, true, true, true, true);
                        }
                    }
                }
            }
        }
        
        
        
//        glBindVertexArray(0);
    }
    
    static void renderScreenFramebuffer(){
        //
        //unbind texture channels
        //
        //What does this mean?
        //essentially there are two channels we're using to draw mesh textures
        //we have to glBindTexture to pointer 0 for BOTH channels, otherwise
        //the leftover texture gets used to draw the screen framebuffer quad
        //which doesnt work
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, 0);
        glActiveTexture(GL_TEXTURE0);
        
        glDisable(GL_DEPTH_TEST);
        glViewport(0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
        
        
        
        //render full screen quad
//        glBlitFramebuffer(0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT, 0, 0, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT,
//                  GL_COLOR_BUFFER_BIT, GL_NEAREST);
        Globals.renderingEngine.setActiveShader(screenTextureShaders);
        glBindVertexArray(screenTextureVAO);
        //aaa
        glBindTexture(GL_TEXTURE_2D, screenFramebuffer.getTexturePointer());
//        glBindTexture(GL_TEXTURE_2D, lightDepthBuffer.getTexturePointer());
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
    }
    
    
    static void renderUI(){
        glDisable(GL_DEPTH_TEST);
        for(Widget currentWidget : Globals.widgetManager.getWidgetList()){
            if(currentWidget.getVisible()){
                currentWidget.draw(GL_DEFAULT_FRAMEBUFFER, Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
            }
        }
        
//        for(Entity currentEntity : Globals.entityManager.getUIElements()){
//            Actor uiActor = EntityUtils.getEntityActor(currentEntity);
//            if(currentEntity.getDataKeys().contains(EntityDataStrings.DATA_STRING_UI_ELEMENT_FONT)){
//                modelTransformMatrix.identity();
//                modelTransformMatrix.translate(EntityUtils.getEntityPosition(currentEntity));
//                uiActor.applyModelMatrix(modelTransformMatrix);
//            }
//            uiActor.drawUI();
//        }
    }
    
    static void renderBlackBackground(){
        //render full screen quad
        glUseProgram(screenTextureShaders.shaderProgram);
        glDisable(GL_DEPTH_TEST);
        glBindVertexArray(screenTextureVAO);
        Texture blackTexture = Globals.assetManager.fetchTexture(Globals.blackTexture);
        if(blackTexture != null){
            blackTexture.bind();
        }
//        glBindTexture(GL_TEXTURE_2D, screenFramebuffer.getTexture());
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
    }
    
    static void renderWhiteBackground(){
        //render full screen quad
        glUseProgram(screenTextureShaders.shaderProgram);
        glDisable(GL_DEPTH_TEST);
        glBindVertexArray(screenTextureVAO);
        Texture blackTexture = Globals.assetManager.fetchTexture(Globals.offWhiteTexture);
        if(blackTexture != null){
            blackTexture.bind();
        }
//        glBindTexture(GL_TEXTURE_2D, screenFramebuffer.getTexture());
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glBindVertexArray(0);
    }
    
    
    public void setActiveShader(ShaderProgram program){
        glUseProgram(program.shaderProgram);
        activeProgram = program;
    }
    
    public ShaderProgram getActiveShader(){
        return activeProgram;
    }
    
    public void bindFramebuffer(int framebufferPointer){
        glBindFramebuffer(GL_FRAMEBUFFER, framebufferPointer);
    }
    
    public void setViewportSize(int width, int height){
        glViewport(0, 0, width, height);
    }
    
    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());
    }
    
}
