// JOGL/GLSL convolution example, adapted from jglmark GLSL example code
// by Chris Brown.
// Loads image as a texture, renders quad using it, with blur shader attached.
// Works.
// You will need to rewrite the tobyte3() image loading code however.
//
// next, need to setup to render the face, copytotex it,
// then read back the results (readimage).

//package GL.JOGL;

import com.sun.opengl.util.texture.Texture;
import com.sun.opengl.util.texture.TextureCoords;
import com.sun.opengl.util.texture.TextureIO;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GL;
import javax.media.opengl.GLException;
import javax.media.opengl.glu.GLU;

import java.nio.*;

import GR.*;	// replace with your own image loading code

/*
 * SimpleGLSLListener.java
 * SimpleGLSLListener Component
 * Created on May 17, 2006, 8:46 AM
 */

/**
 * @author Chris "Crash0veride007" Brown
 * crash0veride007@gmail.com
 * https://jglmark.dev.java.net/
 */

public class SimpleGLSLListener implements GLEventListener
{
  ByteBuffer _pixels;
  int	_width;
  int	_height;

  boolean 	_dotex = true;
  boolean 	_useshader = true;
    
  private long startTime;
  private long fps = 0;
  private long timeUsed;
  //String shadername = "tryout";
  String shadername = "convolve";
  private String VERTEX_SHADER_SOURCE_FILE = shadername + ".vert";
  private String FRAGMENT_SHADER_SOURCE_FILE = shadername + ".frag";
  public int VERTEX_SHADER_ID;
  public int FRAGMENT_SHADER_ID;
  public int SHADER_PROGRAM_ID;

  public SimpleGLSLListener(String imagepath)
  {
    gr[] pic = grUtil.load(imagepath);	// replace
    byte[] pixels1 = tobyte3(pic);
    _pixels = ByteBuffer.allocateDirect(pixels1.length);
    _pixels.put(pixels1);
    _pixels.rewind();
  }
    
  public void initshaders(GL gl) 
  {
    GLSLShaderUtil.CheckShaderExtensions(gl);
    VERTEX_SHADER_ID = GLSLShaderUtil.InitVertexShaderID(gl);
    GLSLShaderUtil.CompileVertexShader(gl, VERTEX_SHADER_ID, VERTEX_SHADER_SOURCE_FILE);
    FRAGMENT_SHADER_ID = GLSLShaderUtil.InitFragmentShaderID(gl);
    GLSLShaderUtil.CompileFragmentShader(gl, FRAGMENT_SHADER_ID, FRAGMENT_SHADER_SOURCE_FILE);
    SHADER_PROGRAM_ID = GLSLShaderUtil.InitShaderProgramID(gl);
    GLSLShaderUtil.LinkShaderProgram(gl, VERTEX_SHADER_ID, FRAGMENT_SHADER_ID, SHADER_PROGRAM_ID);

    if (_useshader) gl.glUseProgramObjectARB(SHADER_PROGRAM_ID); 

    // try to assign the width/height.
    // note that the width, height variables must actually be used
    // in the shader for this to work.  Also, have to do it after
    // using the shader.
    if (true) {
      int width_id = GLSLShaderUtil.AllocateUniform(gl, SHADER_PROGRAM_ID, "width");
      int height_id = GLSLShaderUtil.AllocateUniform(gl, SHADER_PROGRAM_ID, "height");
      gl.glUniform1f(width_id, (float)_width);
      gl.glUniform1f(height_id, (float)_height);
    }

  } //initshaders
     

  public void init(GLAutoDrawable drawable)
  {
    GL gl = drawable.getGL();
    initshaders(gl);

    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glClearDepth(1.0f);
    gl.glEnable(GL.GL_DEPTH_TEST);
    gl.glDepthFunc(GL.GL_LEQUAL);
    gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
    if (_dotex) {
      gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1);	// added for image
      gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 3,
		      _width, _height,
		      0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, _pixels);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
			 GL.GL_NEAREST);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
			 GL.GL_NEAREST);
      gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_DECAL);
      gl.glEnable(GL.GL_TEXTURE_2D);
      gl.glShadeModel(GL.GL_FLAT);
    }
    gl.setSwapInterval(0); // VSYNC Setting 1=ON 0=OFF
    startTime = System.currentTimeMillis() + 5000;
    fps = 0;
  }

  public void display(GLAutoDrawable drawable)
  {
    GL gl = drawable.getGL();
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT );        

    gl.glPushMatrix();
    //if (_useshader) gl.glUseProgramObjectARB(SHADER_PROGRAM_ID); expensive?

    if (_dotex) {
      // draw a quad
      gl.glBegin(GL.GL_QUADS);
      
      gl.glTexCoord2f(0.0f, 0.0f);
      gl.glVertex3f(0f, 0f, 0f);

      gl.glTexCoord2f(1.f, 0.0f);
      gl.glVertex3f(1.f, 0.0f, 0.f);

      gl.glTexCoord2f(1.f, 1.f);
      gl.glVertex3f(1.f, 1.f, 0.f);

      gl.glTexCoord2f(0.0f, 1.f);
      gl.glVertex3f(0.0f, 1.f, 0.f);

      gl.glEnd();
    }

    if (false) {	// draw the image.
      // but this is not what we do for GLSL, need to
      // draw a textured polygon!
      // original example was 1,1 but think 0,0 is correct
      gl.glRasterPos2i( 0, 0);	
      _pixels.rewind();
      gl.glDrawPixels(_width, _height,
		      gl.GL_RGB, gl.GL_UNSIGNED_BYTE,
		      _pixels);
    }

    //if (_useshader) gl.glUseProgramObjectARB(0);	// expensive?
    gl.glPopMatrix();

    if (startTime > System.currentTimeMillis()) {
      fps++;
    } else {
      timeUsed = 5000 + (startTime - System.currentTimeMillis());
      startTime = System.currentTimeMillis() + 5000;
      System.out.println(fps + " frames in " + (float) (timeUsed / 1000f) + " seconds = "+ (fps / (timeUsed / 1000f))+" FPS");
      fps = 0;
    }

    gl.glFlush();
  } //display
    
  public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
    
  public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
  {

    final GL gl = drawable.getGL();
    gl.glViewport(0, 0, width, height);
    gl.glMatrixMode(gl.GL_PROJECTION);
    gl.glLoadIdentity();
    gl.glOrtho(0, 1., 0, 1., -1.0, 1.0);
    gl.glMatrixMode(gl.GL_MODELVIEW);
  }

  //----------------------------------------------------------------

  byte[] tobyte3(gr[] pic)
  {
    _width = pic[0].xres();
    _height = pic[0].yres();
    System.out.println("res = "+_width+","+_height);
    byte[] pixels = new byte[3*_width*_height];
    int off = 0;

    for( int iyy=0; iyy < _height; iyy++ ) {
      int iy = (_height-1) - iyy;
      for( int ix=0; ix < _width; ix++ ) {

	int v = pic[0].rd(ix,iy);
	v = (int)((255.f / gr.DTMAX) * v);
	if (v > 127) v = v - 256;
	pixels[off++] = (byte)v;

	v = pic[1].rd(ix,iy);
	v = (int)((255.f / gr.DTMAX) * v);
	if (v > 127) v = v - 256;
	pixels[off++] = (byte)v;

	v = pic[2].rd(ix,iy);
	v = (int)((255.f / gr.DTMAX) * v);
	if (v > 127) v = v - 256;
	pixels[off++] = (byte)v;

      }
    } //iy

    return pixels;
  } //tobyte3

} //SimpleGLSLListener

