package pl.vorg.mowa.core.graphics;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;

import com.framsticks.net.client3D.Log;

/**
 * Program wykonywany na karcie graficznej.
 * 
 * @author vorg
 *
 */
public class ShaderProgram {
	private int vertexShaderId;
	private int fragmentShaderId;
	private int programId;
	
	private String name;
	private String vertSource;
	private String fragSource;
	
	public static final int TANGENT_ATTRIB_LOCATION = 3;
	
	public String getFragSource() {
		return fragSource;
	}

	public void setFragSource(String fragSource) {
		this.fragSource = fragSource;
	}

	public String getVertSource() {
		return vertSource;
	}

	public void setVertSource(String vertSource) {
		this.vertSource = vertSource;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
	
	public int getProgramId() {
		return programId;
	}

	public void compile() {
		GL gl = GLU.getCurrentGL();
		
		vertexShaderId = gl.glCreateShaderObjectARB(GL.GL_VERTEX_SHADER_ARB);
		gl.glBindAttribLocationARB(programId, TANGENT_ATTRIB_LOCATION, "tangent");
		
		gl.glShaderSourceARB(vertexShaderId, 1, new String[] { vertSource}, null);	
		gl.glCompileShaderARB(vertexShaderId);	
		checkErrors(name + " vert", vertexShaderId);
		
		fragmentShaderId = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB);
		gl.glShaderSourceARB(fragmentShaderId, 1, new String[] { fragSource}, null);	
		gl.glCompileShaderARB(fragmentShaderId);
		checkErrors(name + " frag", fragmentShaderId);
			
		programId = gl.glCreateProgramObjectARB();
		gl.glAttachObjectARB(programId, vertexShaderId);
		gl.glAttachObjectARB(programId, fragmentShaderId);
		checkErrors(name + " program", programId);
		
		gl.glLinkProgramARB(programId);	
		checkErrors(name, programId);		
	}

	public void bind() {
		GL gl = GLU.getCurrentGL();
		gl.glUseProgramObjectARB(programId);
	}
	
	public void unbind() {
		GL gl = GLU.getCurrentGL();
		gl.glUseProgramObjectARB(0);
	}
	
	public void dispose() {
		GL gl = GLU.getCurrentGL();
	
		if (vertexShaderId != 0)  {
			if (programId != 0) {
				gl.glDetachObjectARB(programId, vertexShaderId);
			}
			gl.glDeleteObjectARB(vertexShaderId);		
		}
			
		if (fragmentShaderId != 0) {
			if (programId != 0) {
				gl.glDetachObjectARB(programId, fragmentShaderId);
			}
			gl.glDeleteObjectARB(fragmentShaderId);
		}	
		
		if (programId != 0) {	
			gl.glDeleteObjectARB(programId);
		}
	}
	
	public static void checkErrors(String objectName, int objectId) {
		IntBuffer infologLength = IntBuffer.allocate(1);
		IntBuffer charsWritten = IntBuffer.allocate(1);
		ByteBuffer infoLog;
		
		GL gl = GLU.getCurrentGL();
		gl.glGetObjectParameterivARB(objectId, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, infologLength);

		if (infologLength.get(0) > 0)	{
			infoLog = ByteBuffer.allocate(infologLength.get(0));
			gl.glGetInfoLogARB(objectId, infologLength.get(0), charsWritten, infoLog);
			
			if (charsWritten.get(0) > 0) {
				Log.getInstance().log("dbg", "ShaderProgram.checkErrors " + objectName);
				Log.getInstance().log("err", new String(infoLog.array()));
			}
		}
	}
	
	public static ShaderProgram load(String filePath) throws IOException {
		Log.getInstance().log("dbg", "ShaderProgram.load \"" + filePath + "\"");
		return load(new BufferedReader(new FileReader(filePath)));
	}
	
	private static ShaderProgram load(BufferedReader reader) throws IOException {
		ShaderProgram program = parse(reader);
		program.compile();
		return program;
	}
	
	private static ShaderProgram parse(BufferedReader reader) throws IOException {
		ShaderProgram program = new ShaderProgram();
		StringBuilder fragSrc = new StringBuilder();
		StringBuilder vertSrc = new StringBuilder();
		
		final int VERTEX_SHADER_SRC = 1;
		final int FRAGMENT_SHADER_SRC = 2;
		
		int mode = 0;
		String line;
		while((line = reader.readLine()) != null) {
			if (line.indexOf("//name") == 0) {
				program.setName(line.substring("//name".length()));
			}
			else if (line.indexOf("//vert") == 0) {
				mode = VERTEX_SHADER_SRC;
			}
			else if (line.indexOf("//frag") == 0) {
				mode = FRAGMENT_SHADER_SRC;
			}
			else {
				switch(mode) {
					case VERTEX_SHADER_SRC:
						vertSrc.append(line);
						vertSrc.append("\n");
					break;
					case FRAGMENT_SHADER_SRC:
						fragSrc.append(line);
						fragSrc.append("\n");
					break;
				}
			}
		}
		
		program.setVertSource(vertSrc.toString());
		program.setFragSource(fragSrc.toString());
		
		return program;
	}
	
	public void setParam(String name, int value) {
		GL gl = GLU.getCurrentGL();
		int location = gl.glGetUniformLocationARB(programId, name);
		gl.glUniform1iARB(location, value);
	}
	
	public void setParam(String name, float value) {
		GL gl = GLU.getCurrentGL();
		int location = gl.glGetUniformLocationARB(programId, name);
		gl.glUniform1fARB(location, value);
	}
	
	public void setParam(String name, Vec3 value) {
		GL gl = GLU.getCurrentGL();
		int location = gl.glGetUniformLocationARB(programId, name);
		gl.glUniform3fARB(location, value.getX(), value.getY(), value.getZ());
	}
	
	
	
}
