package electrosphere.renderer.shader;

import static org.lwjgl.opengl.GL11.GL_TRUE;
import static org.lwjgl.opengl.GL20.GL_COMPILE_STATUS;
import static org.lwjgl.opengl.GL20.GL_FRAGMENT_SHADER;
import static org.lwjgl.opengl.GL20.GL_LINK_STATUS;
import static org.lwjgl.opengl.GL20.GL_VERTEX_SHADER;
import static org.lwjgl.opengl.GL20.glAttachShader;
import static org.lwjgl.opengl.GL20.glCompileShader;
import static org.lwjgl.opengl.GL20.glCreateShader;
import static org.lwjgl.opengl.GL20.glDeleteShader;
import static org.lwjgl.opengl.GL20.glGetProgramInfoLog;
import static org.lwjgl.opengl.GL20.glGetProgrami;
import static org.lwjgl.opengl.GL20.glGetShaderi;
import static org.lwjgl.opengl.GL20.glLinkProgram;
import static org.lwjgl.opengl.GL20.glShaderSource;
import static org.lwjgl.opengl.GL32.GL_GEOMETRY_SHADER;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.management.RuntimeErrorException;

import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL40;
import org.lwjgl.system.MemoryStack;

import electrosphere.engine.Globals;
import electrosphere.logger.LoggerInterface;
import electrosphere.renderer.OpenGLState;
import electrosphere.renderer.RenderingEngine;
import electrosphere.util.FileUtils;

/**
 * A shader program
 */
public class ShaderProgram {

    //tracks whether caching should be used or not (to deduplicate opengl calls)
    private static final boolean DISABLE_CACHING = false;

    //
    //Program stuff
    //
    int vertexShader;
    int geometryShader;
    int fragmentShader;
    int shaderId;
    
    
    //
    //Uniform locations
    //
    
    
    //Uniforms
    public Map<Integer,Object> uniformMap = new HashMap<Integer,Object>();

    //keeps track of programs that have already been compiled and returns them instead of recompiling from scratch
    static Map<String,ShaderProgram> alreadyCompiledMap = new HashMap<String,ShaderProgram>();
    
    public static ShaderProgram smart_assemble_shader(boolean ContainsBones, boolean apply_lighting){

        //return shader if it has already been compiled
        String shaderKey = ContainsBones + "-" + apply_lighting;
        if(alreadyCompiledMap.containsKey(shaderKey)){
            return alreadyCompiledMap.get(shaderKey);
        }
        
        String vertex_shader_path = "";
        if(ContainsBones){
            vertex_shader_path = "/Shaders/VertexShader.vs";
        } else {
            vertex_shader_path = "/Shaders/VertexShaderNoBones.vs";
        }
        
        String fragment_shader_path = "/Shaders/FragmentShader.fs";
        //
        //Create ShaderProgram object
        //
        ShaderProgram rVal = new ShaderProgram();
        //
        //Read in shader programs
        //
        String tempForReadingShaders = "";
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(vertex_shader_path)));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();

                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String vertexShaderSource = tempForReadingShaders;
        //This try-catch block reads the FragmentShader source into memory
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(fragment_shader_path)));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();
                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String fragmentShaderSource = tempForReadingShaders;
        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.vertexShader = glCreateShader(GL20.GL_VERTEX_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.vertexShader, vertexShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.vertexShader);
        //The following tests if the vertex shader compiles successfully
        int success;
        success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
        }
        //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader
        rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //This points the opengl shadder object to its proper source
        glShaderSource(rVal.fragmentShader, fragmentShaderSource);
        //This compiles the shader object
        glCompileShader(rVal.fragmentShader);
        //This tests for the success of the compile attempt
        success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
        }
        //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram
        rVal.shaderId = GL40.glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderId, rVal.vertexShader);
        glAttachShader(rVal.shaderId, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderId);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderId, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderId));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        alreadyCompiledMap.put(shaderKey,rVal);
        
        return rVal;
    }


    /**
     * Intelligently assembles a shader for use in OIT part of render pipeline
     * @param ContainsBones True if contains bones
     * @param apply_lighting True if should apply lighting
     * @return The int-pointer to the shader compiled
     */
    public static ShaderProgram smartAssembleOITProgram(boolean ContainsBones, boolean apply_lighting){

        //return shader if it has already been compiled
        String shaderKey = "oit" + ContainsBones + "-" + apply_lighting;
        if(alreadyCompiledMap.containsKey(shaderKey)){
            return alreadyCompiledMap.get(shaderKey);
        }
        
        String vertex_shader_path = "";
        if(ContainsBones){
            vertex_shader_path = "/Shaders/oit/general/VertexShader.vs";
        } else {
            vertex_shader_path = "/Shaders/oit/general/VertexShaderNoBones.vs";
        }
        
        String fragment_shader_path = "/Shaders/oit/general/FragmentShader.fs";
        //
        //Create ShaderProgram object
        //
        ShaderProgram rVal = new ShaderProgram();
        //
        //Read in shader programs
        //
        String tempForReadingShaders = "";
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(vertex_shader_path)));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();

                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String vertexShaderSource = tempForReadingShaders;
        //This try-catch block reads the FragmentShader source into memory
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(FileUtils.getAssetFileAsStream(fragment_shader_path)));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();
                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String fragmentShaderSource = tempForReadingShaders;
        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.vertexShader = glCreateShader(GL20.GL_VERTEX_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.vertexShader, vertexShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.vertexShader);
        //The following tests if the vertex shader compiles successfully
        int success;
        success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
        }
        //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader
        rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //This points the opengl shadder object to its proper source
        glShaderSource(rVal.fragmentShader, fragmentShaderSource);
        //This compiles the shader object
        glCompileShader(rVal.fragmentShader);
        //This tests for the success of the compile attempt
        success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
        }
        //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram
        rVal.shaderId = GL40.glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderId, rVal.vertexShader);
        glAttachShader(rVal.shaderId, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderId);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderId, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderId));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        alreadyCompiledMap.put(shaderKey,rVal);
        
        return rVal;
    }


    public static ShaderProgram load_default_shader_program(){
        
        
        //
        //Create ShaderProgram object
        //
        ShaderProgram rVal = new ShaderProgram();
        //
        //Read in shader programs
        //
        String tempForReadingShaders = "";
        try {
            BufferedReader br = new BufferedReader(new FileReader(FileUtils.getAssetFile("/Shaders/VertexShader.vs")));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();

                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String vertexShaderSource = tempForReadingShaders;
        //This try-catch block reads the FragmentShader source into memory
        try {
            BufferedReader br = new BufferedReader(new FileReader(FileUtils.getAssetFile("/Shaders/FragmentShader.fs")));
            try {
                StringBuilder sb = new StringBuilder();
                String line = br.readLine();
                while (line != null) {
                        sb.append(line);
                        sb.append(System.lineSeparator());
                        line = br.readLine();
                }
                tempForReadingShaders = sb.toString();
            } finally {
                br.close();
            }
        } catch (IOException e) {
        }
        String fragmentShaderSource = tempForReadingShaders;
        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.vertexShader = glCreateShader(GL_VERTEX_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.vertexShader, vertexShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.vertexShader);
        //The following tests if the vertex shader compiles successfully
        int success;
        success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
        }
        //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader
        rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //This points the opengl shadder object to its proper source
        glShaderSource(rVal.fragmentShader, fragmentShaderSource);
        //This compiles the shader object
        glCompileShader(rVal.fragmentShader);
        //This tests for the success of the compile attempt
        success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
        }
        //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram
        rVal.shaderId = GL40.glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderId, rVal.vertexShader);
        glAttachShader(rVal.shaderId, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderId);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderId, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderId));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        return rVal;
    }
    
    public static ShaderProgram loadSpecificShader(String vertexPath, String fragmentPath){
        ShaderProgram rVal = new ShaderProgram();
        
        //
        //Read in shader programs
        //
        String vertexShaderSource = "";
        String fragmentShaderSource = "";
        try {
            vertexShaderSource = FileUtils.getAssetFileAsString(vertexPath);
            fragmentShaderSource = FileUtils.getAssetFileAsString(fragmentPath);
        } catch(IOException ex){
            
        }
        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.vertexShader = glCreateShader(GL_VERTEX_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.vertexShader, vertexShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.vertexShader);
        //The following tests if the vertex shader compiles successfully
        int success;
        success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            List<Object> errorLines = new LinkedList<Object>();
            LoggerInterface.loggerRenderer.WARNING("Failed to load " + vertexPath + " ... attempting alternatives");
            //report failed to load shader
            errorLines.add("Vertex Shader failed to compile!");
            errorLines.add("Source File is: " + vertexPath);
            errorLines.add("Source is: ");
            errorLines.add(GL20.glGetShaderSource(rVal.vertexShader));
            errorLines.add(new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
            // LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
            // LoggerInterface.loggerRenderer.WARNING("Source File is: " + vertexPath);
            // LoggerInterface.loggerRenderer.WARNING("Source is: ");
            // LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
            // LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
            //attempt loading alternative shaders
            List<String> availableAlternatives = Globals.shaderOptionMap.getAlternativesForFile(vertexPath);
            int alternativesAttempted = 0;
            if(availableAlternatives != null){
                for(String alternative : availableAlternatives){
                    alternativesAttempted++;
                    //load file
                    try {
                        vertexShaderSource = FileUtils.getAssetFileAsString(alternative);
                    } catch (IOException e) {
                        LoggerInterface.loggerEngine.ERROR("Failed to load shader alternative " + alternative, e);
                    }
                    //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
                    rVal.vertexShader = glCreateShader(GL_VERTEX_SHADER);
                    //This alerts openGL to the presence of a vertex shader and points the shader at its source
                    glShaderSource(rVal.vertexShader, vertexShaderSource);
                    //Compiles the source for the vertex shader object
                    glCompileShader(rVal.vertexShader);
                    //The following tests if the vertex shader compiles successfully
                    success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
                    if (success == GL_TRUE) {
                        LoggerInterface.loggerRenderer.WARNING("Successfully loaded alternative shader " + alternative);
                        break;
                    } else {
                        errorLines.add("Vertex Shader failed to compile!");
                        errorLines.add("Source File is: " + vertexPath);
                        errorLines.add("Source is: ");
                        errorLines.add(GL20.glGetShaderSource(rVal.vertexShader));
                        errorLines.add(new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
                        // LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
                        // LoggerInterface.loggerRenderer.WARNING("Source File is: " + vertexPath);
                        // LoggerInterface.loggerRenderer.WARNING("Source is: ");
                        // LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
                        // LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
                    }
                }
                if(success != GL_TRUE){
                    for(Object object : errorLines){
                        if(object instanceof String){
                            LoggerInterface.loggerRenderer.WARNING((String)object);
                        } else if(object instanceof RuntimeErrorException){
                            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", (RuntimeErrorException)object);
                        }
                    }
                }
                LoggerInterface.loggerRenderer.WARNING("Attempted " + alternativesAttempted + " alternative shaders");
            }
        }
        //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader
        rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //This points the opengl shadder object to its proper source
        glShaderSource(rVal.fragmentShader, fragmentShaderSource);
        //This compiles the shader object
        glCompileShader(rVal.fragmentShader);
        //This tests for the success of the compile attempt
        success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            List<Object> errorLines = new LinkedList<Object>();
            LoggerInterface.loggerRenderer.WARNING("Failed to load " + fragmentPath + " ... attempting alternatives");
            //report failed to load shader
            errorLines.add("Fragment Shader failed to compile!");
            errorLines.add("Source File is: " + fragmentPath);
            errorLines.add("Source is: ");
            errorLines.add(GL20.glGetShaderSource(rVal.fragmentShader));
            errorLines.add(new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
            // LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
            // LoggerInterface.loggerRenderer.WARNING("Source File is: " + fragmentPath);
            // LoggerInterface.loggerRenderer.WARNING("Source is: ");
            // LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
            // LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
            //attempt loading alternative shaders
            List<String> availableAlternatives = Globals.shaderOptionMap.getAlternativesForFile(fragmentPath);
            int alternativesAttempted = 0;
            if(availableAlternatives != null){
                for(String alternative : availableAlternatives){
                    alternativesAttempted++;
                    //load file
                    try {
                        fragmentShaderSource = FileUtils.getAssetFileAsString(alternative);
                    } catch (IOException e) {
                        LoggerInterface.loggerEngine.ERROR("Failed to load shader alternative " + alternative, e);
                    }
                    //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
                    rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
                    //This alerts openGL to the presence of a vertex shader and points the shader at its source
                    glShaderSource(rVal.fragmentShader, fragmentShaderSource);
                    //Compiles the source for the vertex shader object
                    glCompileShader(rVal.fragmentShader);
                    //The following tests if the vertex shader compiles successfully
                    success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
                    if (success == GL_TRUE) {
                        LoggerInterface.loggerRenderer.WARNING("Successfully loaded alternative shader " + alternative);
                        break;
                    } else {
                        errorLines.add("Fragment Shader failed to compile!");
                        errorLines.add("Source File is: " + fragmentPath);
                        errorLines.add("Source is: ");
                        errorLines.add(GL20.glGetShaderSource(rVal.fragmentShader));
                        errorLines.add(new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
                        // LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
                        // LoggerInterface.loggerRenderer.WARNING("Source File is: " + fragmentPath);
                        // LoggerInterface.loggerRenderer.WARNING("Source is: ");
                        // LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
                        // LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
                    }
                }
                if(success != GL_TRUE){
                    for(Object object : errorLines){
                        if(object instanceof String){
                            LoggerInterface.loggerRenderer.WARNING((String)object);
                        } else if(object instanceof RuntimeErrorException){
                            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", (RuntimeErrorException)object);
                        }
                    }
                }
                LoggerInterface.loggerRenderer.WARNING("Attempted " + alternativesAttempted + " alternative shaders");
            }
        }
        //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram
        rVal.shaderId = GL40.glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderId, rVal.vertexShader);
        glAttachShader(rVal.shaderId, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderId);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderId, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.ERROR(glGetProgramInfoLog(rVal.shaderId), new RuntimeException(glGetProgramInfoLog(rVal.shaderId)));
            LoggerInterface.loggerRenderer.WARNING("Shader sources: " + vertexPath + " " + fragmentPath);
            return Globals.defaultMeshShader;
            // throw new RuntimeException(glGetProgramInfoLog(rVal.shaderProgram));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        return rVal;
    }

    public static ShaderProgram loadSpecificShader(String vertexPath, String geometryPath, String fragmentPath){
        ShaderProgram rVal = new ShaderProgram();
        
        //
        //Read in shader programs
        //
        String vertexShaderSource = "";
        String geometryShaderSource = "";
        String fragmentShaderSource = "";
        try {
            vertexShaderSource = FileUtils.getAssetFileAsString(vertexPath);
            geometryShaderSource = FileUtils.getAssetFileAsString(geometryPath);
            fragmentShaderSource = FileUtils.getAssetFileAsString(fragmentPath);
        } catch(IOException ex){
            
        }
        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.vertexShader = glCreateShader(GL_VERTEX_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.vertexShader, vertexShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.vertexShader);
        //The following tests if the vertex shader compiles successfully
        int success;
        success = glGetShaderi(rVal.vertexShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Vertex Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.vertexShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.vertexShader)));
        }


        //Creates a new shader object and assigns its 'pointer' to the integer "vertexShader"
        rVal.geometryShader = glCreateShader(GL_GEOMETRY_SHADER);
        //This alerts openGL to the presence of a vertex shader and points the shader at its source
        glShaderSource(rVal.geometryShader, geometryShaderSource);
        //Compiles the source for the vertex shader object
        glCompileShader(rVal.geometryShader);
        //The following tests if the vertex shader compiles successfully
        success = glGetShaderi(rVal.geometryShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Geometry Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.geometryShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.geometryShader)));
        }



        //Creates and opengl object for a fragment shader and assigns its 'pointer' to the integer fragmentShader
        rVal.fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //This points the opengl shadder object to its proper source
        glShaderSource(rVal.fragmentShader, fragmentShaderSource);
        //This compiles the shader object
        glCompileShader(rVal.fragmentShader);
        //This tests for the success of the compile attempt
        success = glGetShaderi(rVal.fragmentShader, GL_COMPILE_STATUS);
        if (success != GL_TRUE) {
            LoggerInterface.loggerRenderer.WARNING("Fragment Shader failed to compile!");
            LoggerInterface.loggerRenderer.WARNING("Source is: ");
            LoggerInterface.loggerRenderer.WARNING(GL20.glGetShaderSource(rVal.fragmentShader));
            LoggerInterface.loggerRenderer.ERROR("Runtime Exception", new RuntimeException(GL20.glGetShaderInfoLog(rVal.fragmentShader)));
        }
        //This creates a shader program opengl object and assigns its 'pointer' to the integer shaderProgram
        rVal.shaderId = GL40.glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderId, rVal.vertexShader);
        glAttachShader(rVal.shaderId, rVal.geometryShader);
        glAttachShader(rVal.shaderId, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderId);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderId, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderId));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.geometryShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        return rVal;
    }

    /**
     * Sets the value of a uniform on this shader
     * @param uniformLocation the uniform location
     * @param value the value
     */
    public void setUniform(OpenGLState openGLState, int uniformLocation, Object value){
        if(DISABLE_CACHING || !uniformMap.containsKey(uniformLocation) || !uniformMap.get(uniformLocation).equals(value)){
            try(MemoryStack stack = MemoryStack.stackPush()){
                uniformMap.put(uniformLocation,value);
                if(value instanceof Matrix4f){
                    Matrix4f currentUniform = (Matrix4f)value;
                    GL40.glUniformMatrix4fv(uniformLocation, false, currentUniform.get(new float[16]));
                    Globals.renderingEngine.checkError();
                }
                if(value instanceof Matrix4d){
                    Matrix4d currentUniform = (Matrix4d)value;
                    GL40.glUniformMatrix4fv(uniformLocation, false, currentUniform.get(new float[16]));
                    Globals.renderingEngine.checkError();
                }
                if(value instanceof Vector3f){
                    Vector3f currentUniform = (Vector3f)value;
                    GL40.glUniform3fv(uniformLocation, currentUniform.get(BufferUtils.createFloatBuffer(3)));
                    Globals.renderingEngine.checkError();
                }
                if(value instanceof Integer){
                    GL40.glUniform1i(uniformLocation, (Integer)value);
                    Globals.renderingEngine.checkError();
                }
                if(value instanceof Float){
                    GL40.glUniform1f(uniformLocation, (Float)value);
                    Globals.renderingEngine.checkError();
                }
                if(value instanceof Vector3f){
                    Vector3f vectorView = (Vector3f)value;
                    GL40.glUniform3fv(uniformLocation, vectorView.get(stack.mallocFloat(3)));
                    Globals.renderingEngine.checkError();
                }
            }
        }
    }

    //returned if the uniform isn't found
    public static final int INVALID_UNIFORM_NAME = -1;

    /**
     * Tries to set a uniform
     * @param uniformName The name of the uniform
     * @param value The value to set the uniform to
     */
    public void setUniform(OpenGLState openGLState, String uniformName, Object value){
        //
        //Error checking
        if(uniformName == null || uniformName.equals("")){
            throw new IllegalArgumentException("Trying to set invalid uniform name");
        }
        if(this.getShaderId() != openGLState.getActiveShader().getShaderId()){
            throw new IllegalStateException("Trying to set uniform on shader that is not active");
        }

        //
        //get uniform location
        int uniformLocation = GL40.glGetUniformLocation(this.getShaderId(), uniformName);
        int glErrorCode = Globals.renderingEngine.getError();
        //TODO: fix manually ignoring GL_INVALID_VALUE from call
        if(glErrorCode != 0 && glErrorCode != GL40.GL_INVALID_VALUE){
            LoggerInterface.loggerRenderer.DEBUG_LOOP(RenderingEngine.getErrorInEnglish(glErrorCode));
            LoggerInterface.loggerRenderer.WARNING("Shader id: " + this.getShaderId());
        }

        //
        //set the uniform
        if(uniformLocation == INVALID_UNIFORM_NAME){
            LoggerInterface.loggerRenderer.DEBUG_LOOP("Searched for uniform in a shader that does not contain it. Uniform name: \"" + uniformName + "\"");
        } else {
            this.setUniform(openGLState, uniformLocation, value);
        }
    }


    /**
     * Gets the id of the shader
     * @return The shader id
     */
    public int getShaderId(){
        return shaderId;
    }
    
}
