package electrosphere.renderer;

import electrosphere.logger.LoggerInterface;
import electrosphere.main.Main;
import electrosphere.util.FileUtils;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import org.lwjgl.opengl.GL20;
import static org.lwjgl.opengl.GL20.*;

/**
 *
 * @author amaterasu
 */
public class ShaderProgram {
    //
    //Program stuff
    //
    int vertexShader;
    int fragmentShader;
    int shaderProgram;
    
    
    //
    //Uniform locations
    //
    public int shaderVertexModelLoc;
    public int shaderVertexViewLoc;
    public int shaderVertexProjectionLoc;
    public int shaderVertexViewPosLoc;
    public int shaderVertexBonesLoc;
    public int shaderVertexHasBonesLoc;
    public int shaderVertexNumBonesLoc;
    
    
    //Uniforms
    //list of names of all uniforms in the shader
    public ArrayList<String> uniformList;
    //map
    //string -> tuple
    //tuple: a string describing the type of the data,the current value,location
    //ie arrayVec3,[<1,0,0>,<2,0,0>],colors
    //   Mat4,[Matrix4f],modelMatrix
    public HashMap<String,ArrayList> uniformMap;
    
    public static ShaderProgram smart_assemble_shader(boolean ContainsBones, boolean apply_lighting){
        
        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.shaderProgram = glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderProgram, rVal.vertexShader);
        glAttachShader(rVal.shaderProgram, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderProgram);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderProgram, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderProgram));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        //
        //Set locations
        //
        rVal.shaderVertexModelLoc = glGetUniformLocation(rVal.shaderProgram, "model");
        rVal.shaderVertexViewLoc = glGetUniformLocation(rVal.shaderProgram, "view");
        rVal.shaderVertexProjectionLoc = glGetUniformLocation(rVal.shaderProgram, "projection");
        rVal.shaderVertexViewPosLoc = glGetUniformLocation(rVal.shaderProgram, "viewPos");
        if(ContainsBones){
            rVal.shaderVertexBonesLoc = glGetUniformLocation(rVal.shaderProgram, "bones");
            rVal.shaderVertexNumBonesLoc = glGetUniformLocation(rVal.shaderProgram, "numBones");
        }
        rVal.shaderVertexHasBonesLoc = glGetUniformLocation(rVal.shaderProgram, "hasBones");
        
        
        
        
        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.shaderProgram = glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderProgram, rVal.vertexShader);
        glAttachShader(rVal.shaderProgram, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderProgram);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderProgram, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderProgram));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        //
        //Set locations
        //
        rVal.shaderVertexModelLoc = glGetUniformLocation(rVal.shaderProgram, "model");
        rVal.shaderVertexViewLoc = glGetUniformLocation(rVal.shaderProgram, "view");
        rVal.shaderVertexProjectionLoc = glGetUniformLocation(rVal.shaderProgram, "projection");
        rVal.shaderVertexViewPosLoc = glGetUniformLocation(rVal.shaderProgram, "viewPos");
        rVal.shaderVertexBonesLoc = glGetUniformLocation(rVal.shaderProgram, "bones");
        rVal.shaderVertexNumBonesLoc = glGetUniformLocation(rVal.shaderProgram, "numBones");
        rVal.shaderVertexHasBonesLoc = glGetUniformLocation(rVal.shaderProgram, "hasBones");
        
        
        
        
        return rVal;
    }
    
    public static ShaderProgram loadSpecificShader(String vertexPath, String fragmentPath){
        ShaderProgram rVal = new ShaderProgram();
        
        String vertex_shader_path = vertexPath;
        String fragment_shader_path = fragmentPath;
        //
        //Read in shader programs
        //
        String vertexShaderSource = "";
        String fragmentShaderSource = "";
        try {
            vertexShaderSource = FileUtils.getAssetFileAsString(vertex_shader_path);
            fragmentShaderSource = FileUtils.getAssetFileAsString(fragment_shader_path);
        } 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 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.shaderProgram = glCreateProgram();
        //This attaches the vertex and fragment shaders to the program
        glAttachShader(rVal.shaderProgram, rVal.vertexShader);
        glAttachShader(rVal.shaderProgram, rVal.fragmentShader);
        //This links the program to the GPU (I think its to the GPU anyway)
        glLinkProgram(rVal.shaderProgram);
        //Tests for the success of the shader program creation
        success = glGetProgrami(rVal.shaderProgram, GL_LINK_STATUS);
        if (success != GL_TRUE) {
            throw new RuntimeException(glGetProgramInfoLog(rVal.shaderProgram));
        }        
        
        //Deletes the individual shader objects to free up memory
        glDeleteShader(rVal.vertexShader);
        glDeleteShader(rVal.fragmentShader);
        
        
        
        //
        //Set locations
        //
        rVal.shaderVertexModelLoc = glGetUniformLocation(rVal.shaderProgram, "model");
        rVal.shaderVertexViewLoc = glGetUniformLocation(rVal.shaderProgram, "view");
        rVal.shaderVertexProjectionLoc = glGetUniformLocation(rVal.shaderProgram, "projection");
        rVal.shaderVertexViewPosLoc = glGetUniformLocation(rVal.shaderProgram, "viewPos");
        
        
        
        
        return rVal;
    }
    
}
