/**
 * This package provides classes to facilitate the handling of opengl textures, glsl shaders and 
 * off-screen rendering in Processing.
 * @author Andres Colubri
 * @version 0.8.9.4
 *
 * Copyright (c) 2008 Andres Colubri
 *
 * This source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This code is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * A copy of the GNU General Public License is available on the World
 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
 * obtain it by writing to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package codeanticode.glgraphics; 
 
import processing.core.*;
import processing.opengl.*;
 
import javax.media.opengl.*;
import com.sun.opengl.util.*;
import java.nio.*;

// ToDo: 
// Add support for CG shaders (they work on Intel X3100 GPUs). 
// Define an abstract base GLShader from which GLSLShader and GLCGShader class are derived.

/**
 * This class encapsulates a glsl shader. Based in the code by JohnG (http://www.hardcorepawn.com/)
 */
public class GLSLShader
{
    /**
     * Creates an instance of GLSLShader.
     * @param parent PApplet
     */
    public GLSLShader(PApplet parent)
    {
        this.parent = parent;
       
        pgl = (PGraphicsOpenGL)parent.g;
        gl = pgl.gl;
		
        programObject = gl.glCreateProgramObjectARB();
        vertexShader = -1;
        geometryShader = -1;        
        fragmentShader = -1;
    }

    /**
     * Loads and compiles the vertex shader contained in file.
     * @param file String
     */	
    public void loadVertexShader(String file)
    {
        String shaderSource = PApplet.join(parent.loadStrings(file), "\n");
        vertexShader = gl.glCreateShaderObjectARB(GL.GL_VERTEX_SHADER_ARB);
        gl.glShaderSourceARB(vertexShader, 1, new String[]{shaderSource}, (int[]) null, 0);
        gl.glCompileShaderARB(vertexShader);
        checkLogInfo("Vertex shader " + file + " compilation: ", vertexShader);
        gl.glAttachObjectARB(programObject, vertexShader);        
    }

    /**
     * Loads and compiles the geometry shader contained in file.
     * @param file String
     */	
    public void loadGeometryShader(String file)
    {
        String shaderSource = PApplet.join(parent.loadStrings(file), "\n");
        geometryShader = gl.glCreateShaderObjectARB(GL.GL_GEOMETRY_SHADER_EXT);
        gl.glShaderSourceARB(geometryShader, 1, new String[]{shaderSource},(int[]) null, 0);
        gl.glCompileShaderARB(geometryShader);
        checkLogInfo("Geometry shader " + file + " compilation: ", geometryShader);
        gl.glAttachObjectARB(geometryShader, geometryShader);
    }

    /**
     * Loads and compiles the fragment shader contained in file.
     * @param file String
     */	
    public void loadFragmentShader(String file)
    {
        String shaderSource = PApplet.join(parent.loadStrings(file), "\n");
        fragmentShader = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB);
        gl.glShaderSourceARB(fragmentShader, 1, new String[]{shaderSource},(int[]) null, 0);
        gl.glCompileShaderARB(fragmentShader);
        checkLogInfo("Fragment shader " + file + " compilation: ", fragmentShader);
        gl.glAttachObjectARB(programObject, fragmentShader);
    }
    
    /**
     * Returns the ID location of the attribute parameter given its name.
     * @param name String
     * @return int
     */
    public int getAttribLocation(String name)
    {
        return(gl.glGetAttribLocationARB(programObject, name));
    }

    /**
     * Returns the ID location of the uniform parameter given its name.
     * @param name String
     * @return int
     */
    public int getUniformLocation(String name)
    {
        return(gl.glGetUniformLocationARB(programObject, name));
    }

    /**
     * Configures the geometry shader by setting the primitive types that it will take as input
     * and return as output, and the maximum number of vertices that will generate. This method
     * needs to be called after creating the geometry shader and before linking the program. 
     * @param inGeoPrim String
     * @param outGeoPrim String 
     * @param maxNumOutVert int
     */    
    public void setupGeometryShader(String inGeoPrim, String outGeoPrim, int maxNumOutVert)
    {
        // Setting up the geometry shader.
    	
    	int inGeo = GLUtils.parsePrimitiveType(inGeoPrim);
    	int outGeo = GLUtils.parsePrimitiveType(outGeoPrim);
    	
        // First, what type of primitive this shader will accept.
	    gl.glProgramParameteriEXT(programObject, GL.GL_GEOMETRY_INPUT_TYPE_EXT, inGeo);
        // Second, what type of primitive this shader will output.
        gl.glProgramParameteriEXT(programObject, GL.GL_GEOMETRY_OUTPUT_TYPE_EXT, outGeo);

        // Third, setting the maximum number of vertices that the shader can output.
        // Using the maximum allowed value if the parameter is 0 or invalid.
	    IntBuffer buf = IntBuffer.allocate(1);
	    int n;
	    gl.glGetIntegerv(GL.GL_MAX_GEOMETRY_OUTPUT_VERTICES_EXT, buf);
	    n = buf.get(0);
        if ((0 < maxNumOutVert) && (maxNumOutVert < n)) n = maxNumOutVert;  
	
	    gl.glProgramParameteriEXT(programObject, GL.GL_GEOMETRY_VERTICES_OUT_EXT, n);
    }    
    
    /**
     * Links the shader program and validates it.
     */	
    public void linkProgram()
    {
        gl.glLinkProgramARB(programObject);
        gl.glValidateProgramARB(programObject);
        checkLogInfo("GLSL program validation: ", programObject);
    }

    /**
     * Starts the execution of the shader program.
     */
    public void start()
    {
        gl.glUseProgramObjectARB(programObject);
    }

    /**
     * Stops the execution of the shader program.
     */	
    public void stop()
    {
        gl.glUseProgramObjectARB(0);
    }

    /**
     * @invisible
     * Check the log error for the opengl object obj. Prints error message if needed.
     */		
    protected void checkLogInfo(String title, int obj)
    {
        IntBuffer iVal = BufferUtil.newIntBuffer(1);
        gl.glGetObjectParameterivARB(obj, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, iVal);

        int length = iVal.get();
        
        if (length <= 1) 
        {
            return;
        }    
        
        // Some error ocurred...
        ByteBuffer infoLog = BufferUtil.newByteBuffer(length);
        iVal.flip();
        gl.glGetInfoLogARB(obj, length, iVal, infoLog);
        byte[] infoBytes = new byte[length];
        infoLog.get(infoBytes);
        System.err.println(title);
        System.err.println(new String(infoBytes));
    }
    
    /**
     * @invisible
     */
    protected PApplet parent;
	
    /**
     * @invisible
     */
    protected GL gl;
	
    /**
     * @invisible
     */
    protected PGraphicsOpenGL pgl;
	
    /**
     * @invisible
     */
    protected int programObject;
	
    /**
     * @invisible
     */		
    protected int vertexShader;

    /**
     * @invisible
     */		
    protected int geometryShader;
    
    /**
     * @invisible
     */
    protected int fragmentShader;    
}
