package electrosphere.renderer.light;

import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL31.GL_UNIFORM_BUFFER;

import java.nio.ByteBuffer;

import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;

import electrosphere.engine.Globals;
import electrosphere.entity.types.camera.CameraEntityUtils;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.shader.ShaderProgram;

/**
 * Manages the light sources in the engine
 */
public class LightManager {
    
    final static int BIND_POINT = 1;
    final static int POINT_LIGHT_COUNT = 10;
    final static int BUFFER_SIZE = 1184;
    int uboIndex;
    ByteBuffer dataBuffer;

    //sun position (For shadow maps)
    Vector3f sunPosition = new Vector3f(0.2f,-1.0f,0.3f);

    //lights
    DirectionalLight directionalLight;
    PointLight[] pointLights;

    public LightManager(){

        //create data buffer
        dataBuffer = BufferUtils.createByteBuffer(BUFFER_SIZE);

        uboIndex = glGenBuffers();
        glBindBuffer(GL_UNIFORM_BUFFER, uboIndex);
        glBufferData(GL_UNIFORM_BUFFER, BUFFER_SIZE, GL_DYNAMIC_DRAW); // allocate 152 bytes of memory
        //set range to full size
        int offset = 0;
        GL30.glBindBufferRange(GL_UNIFORM_BUFFER, BIND_POINT, uboIndex, offset, BUFFER_SIZE);
        //unbind
        glBindBuffer(GL_UNIFORM_BUFFER, 0);

        //create directional light
        directionalLight = new DirectionalLight(new Vector3f(1,-1,0).normalize());
        directionalLight.setAmbient(new Vector3f(0.6f,0.6f,0.6f));
        directionalLight.setDiffuse(new Vector3f(0.3f,0.3f,0.3f));
        directionalLight.setDirection(sunPosition);
        directionalLight.setSpecular(new Vector3f(0.0f,0.0f,0.0f));

        pointLights = new PointLight[POINT_LIGHT_COUNT];
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            pointLights[i] = new PointLight(new Vector3f(0,0,0),new Vector3f(0,0,0));
        }

        pointLights[0].setAmbient(new Vector3f(0.924500f, 0.369800f, 0.092450f));
        pointLights[0].setConstant(1.0f);
        pointLights[0].setDiffuse(new Vector3f(0.924500f, 0.369800f, 0.092450f));
        pointLights[0].setLinear(0.7f);
        pointLights[0].setPosition(new Vector3f(1.0f,0.05f,1.0f));
        pointLights[0].setQuadratic(1.8f);
        pointLights[0].setSpecular(new Vector3f(0.0f,0.0f,0.0f));

        // pointLights[0].setAmbient(new Vector3f(0.924500f, 0.369800f, 0.092450f));
        // pointLights[0].setConstant(1.0f);
        // pointLights[0].setDiffuse(new Vector3f(0.924500f, 0.369800f, 0.092450f));
        // pointLights[0].setLinear(0.09f);
        // pointLights[0].setPosition(new Vector3f(1.0f,0.05f,1.0f));
        // pointLights[0].setQuadratic(0.032f);
        // pointLights[0].setSpecular(new Vector3f(0.0f,0.0f,0.0f));





        /*
        Vector3f lightLoc = new Vector3f(0.2f,-1.0f,0.3f);
                float temp[] = new float[3];
                temp[0] = lightLoc.x;
                temp[1] = lightLoc.y;
                temp[2] = lightLoc.z;
                glUniform3fv(glGetUniformLocation(Globals.renderingEngine.getActiveShader().shaderProgram, "dirLight.direction"), temp);

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

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

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

                temp[0] = 32f;
        */

        //glBufferData(GL_UNIFORM_BUFFER, <my_data>, GL_STATIC_DRAW);
    }


    //
    //
    // HIGHER ORDER OBJECT MANIPULATION
    //
    //

    public void setPointLight(PointLight light, int index){
        pointLights[index] = light;
    }

    /**
     * Gets the directional light for this light manager
     * @return the directional light
     */
    public DirectionalLight getDirectionalLight(){
        return this.directionalLight;
    }

















    //
    //
    // BUFFER MANAGEMENT
    //
    //


    /**
     * Call in each mesh / per each shader
     * @param shaderIndex
     */
    public void bindBuffer(OpenGLState openGLState){
        //get position of lights object in shader
        int bufferIndex = GL31.glGetUniformBlockIndex(openGLState.getActiveShader().getShaderId(), "Lights");
        Globals.renderingEngine.checkError();
        if(bufferIndex == ShaderProgram.INVALID_UNIFORM_NAME){
            LoggerInterface.loggerRenderer.INFO("Tried to buffer light manager to shader that does not have it active.");
        } else {
            //bind that position to the slot '2'
            GL31.glUniformBlockBinding(openGLState.getActiveShader().getShaderId(), bufferIndex, BIND_POINT);
            Globals.renderingEngine.checkError();
            //bind our buffer to slot '2' as well
            GL31.glBindBufferBase(GL_UNIFORM_BUFFER, BIND_POINT, uboIndex);
            Globals.renderingEngine.checkError();
        }
        //alternatively if want to use range, do glBindBufferRange(GL_UNIFORM_BUFFER, 2, uboExampleBlock, 0, 152);
        // Globals.renderingEngine.checkError();
    }

    /**
     * Call once per render loop to set buffer values
     */
    public void updateData(){
        putDataInBuffer();
        bufferData();
    }

    void bufferData(){
        glBindBuffer(GL_UNIFORM_BUFFER, uboIndex);
        // int offset = 0;
        glBufferData(GL_UNIFORM_BUFFER, dataBuffer, GL_DYNAMIC_DRAW);
        // glBufferSubData(GL_UNIFORM_BUFFER, offset, dataBuffer); 
        glBindBuffer(GL_UNIFORM_BUFFER, 0);
    }

    void putDataInBuffer(){
        //implicitly flips
        dataBuffer.clear();

        //direct light
        //vec3 dirLightDirection
        dataBuffer.putFloat(directionalLight.direction.x);
        dataBuffer.putFloat(directionalLight.direction.y);
        dataBuffer.putFloat(directionalLight.direction.z);
        dataBuffer.putFloat(0);
        //vec3 dirLightAmbient
        dataBuffer.putFloat(directionalLight.ambient.x);
        dataBuffer.putFloat(directionalLight.ambient.y);
        dataBuffer.putFloat(directionalLight.ambient.z);
        dataBuffer.putFloat(0);
        //vec3 dirLightDiffuse
        dataBuffer.putFloat(directionalLight.diffuse.x);
        dataBuffer.putFloat(directionalLight.diffuse.y);
        dataBuffer.putFloat(directionalLight.diffuse.z);
        dataBuffer.putFloat(0);
        //vec3 dirLightSpecular
        dataBuffer.putFloat(directionalLight.specular.x);
        dataBuffer.putFloat(directionalLight.specular.y);
        dataBuffer.putFloat(directionalLight.specular.z);
        dataBuffer.putFloat(0);

        //point light
        //vec3 position[10]
        //dont sub cam
        // for(int i = 0; i < POINT_LIGHT_COUNT; i++){
        //     PointLight light = pointLights[i];
        //     dataBuffer.putFloat(light.position.x);
        //     dataBuffer.putFloat(light.position.y);
        //     dataBuffer.putFloat(light.position.z);
        //     dataBuffer.putFloat(0);
        // }
        //do sub cam
        if(Globals.playerCamera != null){
            Vector3f cameraPos = CameraEntityUtils.getCameraCenter(Globals.playerCamera);
            for(int i = 0; i < POINT_LIGHT_COUNT; i++){
                PointLight light = pointLights[i];
                dataBuffer.putFloat(light.position.x - cameraPos.x);
                dataBuffer.putFloat(light.position.y - cameraPos.y);
                dataBuffer.putFloat(light.position.z - cameraPos.z);
                dataBuffer.putFloat(0);
            }
        }
        //float constant[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.constant);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
        }
        //float linear[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.linear);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
        }
        //float quadratic[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.quadratic);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
            dataBuffer.putFloat(0);
        }
        //vec3 ambient[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.ambient.x);
            dataBuffer.putFloat(light.ambient.y);
            dataBuffer.putFloat(light.ambient.z);
            dataBuffer.putFloat(0);
        }
        //vec3 diffuse[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.diffuse.x);
            dataBuffer.putFloat(light.diffuse.y);
            dataBuffer.putFloat(light.diffuse.z);
            dataBuffer.putFloat(0);
        }
        //vec3 specular[10]
        for(int i = 0; i < POINT_LIGHT_COUNT; i++){
            PointLight light = pointLights[i];
            dataBuffer.putFloat(light.specular.x);
            dataBuffer.putFloat(light.specular.y);
            dataBuffer.putFloat(light.specular.z);
            dataBuffer.putFloat(0);
        }

        dataBuffer.flip();

        // dataBuffer.position(0);

    }

}