package theGhastModding.midiPlayerGL.shaders;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.FloatBuffer;

import org.joml.Matrix4f;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;

public abstract class ShaderProgram {
	
	private int programID;
	private int vertexShaderID;
	private int fragmentShaderID;
	
	private static FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16);
	
	public ShaderProgram(String vertexFile, String fragmentFile) {
		vertexShaderID = loadShader(vertexFile, GL20.GL_VERTEX_SHADER);
		fragmentShaderID = loadShader(fragmentFile, GL20.GL_FRAGMENT_SHADER);
		programID = GL20.glCreateProgram();
		GL20.glAttachShader(programID, vertexShaderID);
		GL20.glAttachShader(programID, fragmentShaderID);
		bindAttributes();
		GL20.glLinkProgram(programID);
		GL20.glValidateProgram(programID);
		getAllUniformLocations();
	}
	
	protected abstract void getAllUniformLocations();
	
	protected int getUniformLocation(String uniformName) {
		return GL20.glGetUniformLocation(programID, uniformName);
	}
	
	public void start() {
		GL20.glUseProgram(programID);
	}
	
	public void stop() {
		GL20.glUseProgram(0);
	}
	
	protected abstract void bindAttributes();
	
	protected void bindAttribute(int attribute, String varName) {
		GL20.glBindAttribLocation(programID, attribute, varName);
	}
	
	protected void loadFloat(int location, float value) {
		GL20.glUniform1f(location, value);
	}
	
	protected void loadVector3f(int location, Vector3f vector) {
		GL20.glUniform3f(location, vector.x(), vector.y(), vector.z());
	}
	
	protected void loadVector2f(int location, Vector2f vector) {
		GL20.glUniform2f(location, vector.x(), vector.y());
	}
	
	protected void loadBoolean(int location, boolean value) {
		loadFloat(location, value ? 1.0f : 0.0f);
	}
	
	protected void loadMatrix(int location, Matrix4f matrix) {
		GL20.glUniformMatrix4fv(location, false, matrix.get(matrixBuffer));
	}
	
	private static int loadShader(String file, int type) {
		String shaderSource = "";
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(ShaderProgram.class.getResourceAsStream(file)));
			String line;
			while((line = reader.readLine()) != null) {
				shaderSource += line + "\n";
			}
			reader.close();
		}catch(Exception e) {
			System.err.println("Error loading shaders");
			e.printStackTrace();
			GLFW.glfwTerminate();
			GL.destroy();
			System.exit(1);
		}
		int shaderID = GL20.glCreateShader(type);
		GL20.glShaderSource(shaderID, shaderSource);
		GL20.glCompileShader(shaderID);
		if(GL20.glGetShaderi(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
			System.err.println("Could not compile shader: ");
			System.err.println(GL20.glGetShaderInfoLog(shaderID, 512));
			GLFW.glfwTerminate();
			GL.destroy();
			System.exit(1);
		}
		return shaderID;
	}
	
	public void cleanUp() {
		stop();
		GL20.glDetachShader(programID, vertexShaderID);
		GL20.glDetachShader(programID, fragmentShaderID);
		GL20.glDeleteShader(vertexShaderID);
		GL20.glDeleteShader(fragmentShaderID);
		GL20.glDeleteProgram(programID);
	}
	
}