package electrosphere.renderer.shader;

import java.util.Map;

import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Vector2d;
import org.joml.Vector2i;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3i;
import org.joml.Vector4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL40;
import org.lwjgl.system.MemoryStack;

import electrosphere.engine.Globals;
import electrosphere.renderer.OpenGLState;

/**
 * Utility functions for shaders
 */
public class ShaderUtils {
    
    /**
     * Sets the value of a uniform on this shader
     * @param uniformLocation the uniform location
     * @param value the value
     */
    protected static void setUniform(OpenGLState openGLState, Map<Integer,Object> uniformMap, int uniformLocation, Object value){
        if(
            OpenGLState.DISABLE_CACHING ||
            !uniformMap.containsKey(uniformLocation) ||
            !uniformMap.get(uniformLocation).equals(value)
        ){
            try(MemoryStack stack = MemoryStack.stackPush()){

                //
                //matrix4f
                if(value instanceof Matrix4f){
                    Matrix4f currentUniform = (Matrix4f)value;
                    GL40.glUniformMatrix4fv(uniformLocation, false, currentUniform.get(BufferUtils.createFloatBuffer(16)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Matrix4f(currentUniform)); //create new matrix4f to break pointer-matching with equals on cache check

                    //
                    //matrix4d
                } else if(value instanceof Matrix4d){
                    Matrix4d currentUniform = (Matrix4d)value;
                    GL40.glUniformMatrix4fv(uniformLocation, false, currentUniform.get(BufferUtils.createFloatBuffer(16)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Matrix4d(currentUniform)); //create new matrix4f to break pointer-matching with equals on cache check

                    //
                    //vector4f
                } else if(value instanceof Vector4f){
                    Vector4f currentUniform = (Vector4f)value;
                    GL40.glUniform4fv(uniformLocation, currentUniform.get(BufferUtils.createFloatBuffer(4)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector4f(currentUniform)); //create new vector3f to break pointer-matching with equals on cache check

                    //
                    //vector3d
                } else if(value instanceof Vector3f){
                    Vector3f currentUniform = (Vector3f)value;
                    GL40.glUniform3fv(uniformLocation, currentUniform.get(BufferUtils.createFloatBuffer(3)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector3f(currentUniform)); //create new vector3f to break pointer-matching with equals on cache check

                    //
                    //vector3d
                } else if(value instanceof Vector3d){
                    Vector3d currentUniform = (Vector3d)value;
                    GL40.glUniform3dv(uniformLocation, currentUniform.get(BufferUtils.createDoubleBuffer(3)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector3d(currentUniform)); //create new vector3d to break pointer-matching with equals on cache check

                    //
                    //vector2d
                } else if(value instanceof Vector2d){
                    Vector2d currentUniform = (Vector2d)value;
                    GL40.glUniform2dv(uniformLocation, currentUniform.get(BufferUtils.createDoubleBuffer(2)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector2d(currentUniform)); //create new vector2d to break pointer-matching with equals on cache check

                    //
                    //Vector3i
                } else if(value instanceof Vector3i){
                    Vector3i currentUniform = (Vector3i)value;
                    GL40.glUniform3uiv(uniformLocation, currentUniform.get(BufferUtils.createIntBuffer(3)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector3i(currentUniform)); //create new vector2d to break pointer-matching with equals on cache check

                    //
                    //Vector2i
                } else if(value instanceof Vector2i){
                    Vector2i currentUniform = (Vector2i)value;
                    GL40.glUniform2uiv(uniformLocation, currentUniform.get(BufferUtils.createIntBuffer(2)));
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,new Vector2i(currentUniform)); //create new vector2d to break pointer-matching with equals on cache check

                    //
                    //integer
                } else if(value instanceof Integer){
                    GL40.glUniform1i(uniformLocation, (Integer)value);
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,(Integer)value);

                    //
                    //float
                } else if(value instanceof Float){
                    GL40.glUniform1f(uniformLocation, (Float)value);
                    Globals.renderingEngine.checkError();
                    uniformMap.put(uniformLocation,(Float)value);

                } else {
                    throw new UnsupportedOperationException("Tried to set uniform with unsupported type!");
                }
            }
        }
    }

}
