/**
 * This package provides classes to facilitate the handling of opengl textures, glsl shaders and 
 * off-screen rendering in Processing.
 * @author Andres Colubri
 * @version 0.9.3.2
 *
 * 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.io.IOException;
import java.net.URL;
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.glCreateProgram();
        vertexShader = 0;
        geometryShader = 0;        
        fragmentShader = 0;
    }
    
    protected void finalize()  throws Throwable 
    {
        //if (programObject != 0)  gl.glDeleteProgram(programObject);
        //if (vertexShader != 0) gl.glDeleteShader(vertexShader);
        //if (geometryShader != 0) gl.glDeleteShader(geometryShader);
        //if (fragmentShader != 0) gl.glDeleteShader(fragmentShader);
        super.finalize();    
    }        

    /**
     * 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");
        attachVertexShader(shaderSource, file);
    }

    /**
     * Loads and compiles the vertex shader contained in the URL.
     * @param file String
     */
    public void loadVertexShader(URL url) 
    {
    	String shaderSource;
		try {
			shaderSource = PApplet.join(PApplet.loadStrings(url.openStream()),"\n");
			attachVertexShader(shaderSource, url.getFile());
		} catch (IOException e) {
			System.err.println("Cannot load file " + url.getFile() );
		}
    }

    /**
     * @invisible
     * @param shaderSource a string containing the shader's code
     * @param filename the shader's filename, used to print error log information
     */
    private void attachVertexShader(String shaderSource, String file) 
    {
        vertexShader = gl.glCreateShader(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");
        attachGeometryShader(shaderSource, file);
    }

    /**
     * Loads and compiles the geometry shader contained in the URL
     * @param url URL
     */
    public void loadGeometryShader(URL url) 
    {
    	String shaderSource;
		try {
			shaderSource = PApplet.join(PApplet.loadStrings(url.openStream()),"\n");
			attachGeometryShader(shaderSource, url.getFile());
		} catch (IOException e) {
			System.err.println("Cannot load file " + url.getFile() );
		}
    }
    
    /**
     * @invisible
     * @param shaderSource a string containing the shader's code
     * @param filename the shader's filename, used to print error log information
     */
    private void attachGeometryShader(String shaderSource, String file) 
    {
        geometryShader = gl.glCreateShader(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");
        attachFragmentShader(shaderSource, file);
    }
    
    /**
     * Loads and compiles the fragment shader contained in the URL. 
     * @param url URL
     */
    public void loadFragmentShader(URL url) 
    {
    	String shaderSource;
		try {
			shaderSource = PApplet.join(PApplet.loadStrings(url.openStream()),"\n");
			attachFragmentShader(shaderSource, url.getFile());
		} catch (IOException e) {
			System.err.println("Cannot load file " + url.getFile() );
		}
    }
    
    /**
     * @invisible
     * @param shaderSource a string containing the shader's code
     * @param filename the shader's filename, used to print error log information
     */
    private void attachFragmentShader(String shaderSource, String file) 
    {
        fragmentShader = gl.glCreateShader(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));
    }
    
    
    /*

            
GL.GL_BOOL_ARB           
GL.GL_BOOL_VEC2_ARB
GL.GL_BOOL_VEC3_ARB
GL.GL_BOOL_VEC4_ARB
GL.GL_FLOAT
GL.GL_FLOAT_MAT2_ARB
GL.GL_FLOAT_MAT3_ARB
GL.GL_FLOAT_MAT4_ARB
GL.GL_FLOAT_VEC2_ARB
GL.GL_FLOAT_VEC3_ARB
GL.GL_FLOAT_VEC4_ARB
GL.GL_INT
GL.GL_INT_VEC2_ARB
GL.GL_INT_VEC3_ARB
GL.GL_INT_VEC4_ARB             
            
            gl.glUseProgramObjectARB(program);

            // Build a mapping from texture parameters to texture units.
            texUnitMap.clear();
            int count, size;
            int type;
            byte[] paramName = new byte[1024];
            
            int texUnit = 0;

            int[] iVal = { 0 };
            gl.glGetObjectParameterivARB(fragmentProgram, GL.GL_OBJECT_COMPILE_STATUS_ARB, iVal, 0);
            count = iVal[0];
            for (int i = 0; i < count; ++i)
            {
            	IntBuffer iVal1 = BufferUtil.newIntBuffer(1);
            	IntBuffer iVal2 = BufferUtil.newIntBuffer(1);
            	
            	ByteBuffer paramStr = BufferUtil.newByteBuffer(1024);
            	gl.glGetActiveUniformARB(program, i, 1024, null, iVal1, iVal2, paramStr);
                size = iVal1.get();
                type = iVal2.get();
                paramStr.get(paramName);
 
                if ((type == GL.GL_SAMPLER_1D_ARB) ||
                    (type == GL.GL_SAMPLER_2D_ARB) ||
                    (type == GL.GL_SAMPLER_3D_ARB) ||
                    (type == GL.GL_SAMPLER_CUBE_ARB) ||
                    (type == GL.GL_SAMPLER_1D_SHADOW_ARB) ||
                    (type == GL.GL_SAMPLER_2D_SHADOW_ARB) ||
                    (type == GL.GL_SAMPLER_2D_RECT_ARB) ||
                    (type == GL.GL_SAMPLER_2D_RECT_SHADOW_ARB))
                {
                	String strName = new String(paramName, 0, 1024);
                	Integer unit = texUnit;
                    texUnitMap.put(strName, unit);
                    int location = gl.glGetUniformLocationARB(program, strName);
                    gl.glUniform1iARB(location, texUnit);
                    ++texUnit;
                }
            }
           
            gl.glUseProgramObjectARB(0);
        }
    */
    
    
    
    /**
     * @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;    
}
