package electrosphere.renderer.pipelines;

import org.joml.Matrix4d;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL40;

import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityTags;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.types.camera.CameraEntityUtils;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderPipelineState;
import electrosphere.renderer.RenderingEngine;
import electrosphere.renderer.actor.Actor;

/**
 * Updates the volume buffer
 */
public class VolumeBufferPipeline implements RenderPipeline {

    @Override
    public void render(OpenGLState openGLState, RenderPipelineState renderPipelineState) {
        Globals.profiler.beginCpuSample("VolumeBufferPipeline.render");
        Matrix4d modelTransformMatrix = new Matrix4d();
        
        //set the viewport to shadow map size
        openGLState.glViewport(Globals.WINDOW_WIDTH, Globals.WINDOW_HEIGHT);
        openGLState.glDepthTest(true);
        openGLState.glDepthFunc(GL40.GL_LESS);
        GL40.glDepthMask(true);
        
        //stop rendering front faces
        GL40.glEnable(GL40.GL_CULL_FACE);
        GL40.glCullFace(GL40.GL_FRONT);

        openGLState.setActiveShader(renderPipelineState, RenderingEngine.volumeDepthShaderProgram);
        
        RenderingEngine.volumeDepthBackfaceFramebuffer.bind(openGLState);
        GL40.glClear(GL40.GL_DEPTH_BUFFER_BIT);
        openGLState.glActiveTexture(GL40.GL_TEXTURE0);
//            glBindTexture(GL_TEXTURE_2D, woodTexture);
//            renderScene(simpleDepthShader);

        GL40.glUniformMatrix4fv(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "view"), false, Globals.viewMatrix.get(new float[16]));
        // glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "projection"), false, Globals.projectionMatrix.get(new float[16]));
        
        
        
        GL40.glUniform1f(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "linearCoef"), RenderingEngine.volumeDepthLinearCoef);
        GL40.glUniform1f(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "quadCoef"), RenderingEngine.volumeDepthQuadCoef);

        GL40.glUniform1f(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "near"), 0.1f);
        GL40.glUniform1f(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "far"), 100f);
        
//        glCullFace(GL_FRONT);

        //
        // Set render pipeline state
        //
        renderPipelineState.setUseMeshShader(false);
        renderPipelineState.setBufferStandardUniforms(false);
        renderPipelineState.setBufferNonStandardUniforms(true);
        renderPipelineState.setUseMaterial(false);
        renderPipelineState.setUseShadowMap(false);
        renderPipelineState.setUseBones(true);
        renderPipelineState.setUseLight(false);


        //
        //     D R A W     A L L     E N T I T I E S
        //
        for(Entity currentEntity : Globals.clientScene.getEntitiesWithTag(EntityTags.DRAWABLE)){
            Vector3d position = EntityUtils.getPosition(currentEntity);
            if(
                    currentEntity.getData(EntityDataStrings.DATA_STRING_DRAW)!=null && 
                    currentEntity.containsKey(EntityDataStrings.DRAW_VOLUMETRIC)
                    ){
                //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));
                //set projection matrix
                // if(cameraModifiedPosition.length() > 2f){
                //     glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "projection"), false, farVolumeProjectionMatrix.get(new float[16]));
                // } else if(cameraModifiedPosition.length() > 0.5f){
                //     glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "projection"), false, midVolumeProjectionMatrix.get(new float[16]));
                // } else {
                    GL40.glUniformMatrix4fv(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "projection"), false, RenderingEngine.nearVolumeProjectionMatrix.get(new float[16]));
                // }
                // glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "projection"), false, Globals.projectionMatrix.get(new float[16]));
                //calculate and apply model transform
                modelTransformMatrix = modelTransformMatrix.identity();
                modelTransformMatrix.translate(cameraModifiedPosition);
                modelTransformMatrix.rotate(EntityUtils.getRotation(currentEntity));
                modelTransformMatrix.scale(new Vector3d(EntityUtils.getScale(currentEntity)));
                currentActor.applySpatialData(modelTransformMatrix,position);
                //draw
//                if(!currentEntity.getDataKeys().contains(EntityDataStrings.TERRAIN_IS_TERRAIN) && !currentEntity.getDataKeys().contains(EntityDataStrings.DATA_STRING_CREATURE_IS_CREATURE)){
                currentActor.draw(renderPipelineState,openGLState);
//                    System.out.println(currentActor.modelPath);
//                }
            }
        }

        //
        //Draw front faces of all non-volumetrics
        //
        for(Entity currentEntity : Globals.clientScene.getEntitiesWithTag(EntityTags.DRAWABLE)){
            Vector3d position = EntityUtils.getPosition(currentEntity);
            if(
                    (boolean)currentEntity.getData(EntityDataStrings.DATA_STRING_DRAW) && 
                    !currentEntity.containsKey(EntityDataStrings.DRAW_VOLUMETRIC)
                    ){
                //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));
                //set projection matrix
                GL40.glUniformMatrix4fv(GL40.glGetUniformLocation(openGLState.getActiveShader().getShaderId(), "projection"), false, RenderingEngine.nearVolumeProjectionMatrix.get(new float[16]));
                modelTransformMatrix = modelTransformMatrix.identity();
                modelTransformMatrix.translate(cameraModifiedPosition);
                modelTransformMatrix.rotate(EntityUtils.getRotation(currentEntity));
                modelTransformMatrix.scale(new Vector3d(EntityUtils.getScale(currentEntity)));
                currentActor.applySpatialData(modelTransformMatrix,position);
                currentActor.draw(renderPipelineState,openGLState);
            }
        }



        //stop rendering front faces
        GL40.glEnable(GL40.GL_CULL_FACE);
        GL40.glCullFace(GL40.GL_BACK);

        openGLState.setActiveShader(renderPipelineState, RenderingEngine.volumeDepthShaderProgram);
        
        RenderingEngine.volumeDepthFrontfaceFramebuffer.bind(openGLState);
        GL40.glClear(GL40.GL_DEPTH_BUFFER_BIT);
        openGLState.glActiveTexture(GL40.GL_TEXTURE0);
//            glBindTexture(GL_TEXTURE_2D, woodTexture);
//            renderScene(simpleDepthShader);

        // glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "view"), false, Globals.viewMatrix.get(new float[16]));
        // glUniformMatrix4fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "projection"), false, Globals.projectionMatrix.get(new float[16]));
        
        
        
        // GL20.glUniform1f(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "linearCoef"), volumeDepthLinearCoef);
        // GL20.glUniform1f(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "quadCoef"), volumeDepthQuadCoef);
        
//        glCullFace(GL_FRONT);

        //
        //     D R A W     A L L     E N T I T I E S
        //
        for(Entity currentEntity : Globals.clientScene.getEntitiesWithTag(EntityTags.DRAWABLE)){
            Vector3d position = EntityUtils.getPosition(currentEntity);
            if(
                    (boolean)currentEntity.getData(EntityDataStrings.DATA_STRING_DRAW) && 
                    currentEntity.containsKey(EntityDataStrings.DRAW_VOLUMETRIC)
                    ){
                //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(new Vector3d(EntityUtils.getScale(currentEntity)));
                currentActor.applySpatialData(modelTransformMatrix,position);
                //draw
//                if(!currentEntity.getDataKeys().contains(EntityDataStrings.TERRAIN_IS_TERRAIN) && !currentEntity.getDataKeys().contains(EntityDataStrings.DATA_STRING_CREATURE_IS_CREATURE)){
                currentActor.draw(renderPipelineState,openGLState);
//                    System.out.println(currentActor.modelPath);
//                }
            }
        }

        GL40.glCullFace(GL40.GL_BACK);
        //now cull back faces
        
        //reset texture
        openGLState.glActiveTexture(GL40.GL_TEXTURE0);
        openGLState.glBindTexture(GL40.GL_TEXTURE_2D, 0);
        //bind default framebuffer
        openGLState.glBindFramebuffer(GL40.GL_FRAMEBUFFER,0);
        //resume culling backface
        GL40.glDisable(GL40.GL_CULL_FACE);

        Globals.profiler.endCpuSample();
    }
    
}
