package com.abraham.gl.streamingtextures;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import javax.media.opengl.DebugGL;
import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

/**
 * @author Matthias Abraham
 *
 */
public class GLEventHandler implements GLEventListener
{
   private static final boolean useDebugGL = false;
   
   private static final int texWidth = 1024;
   private static final int texHeight = 1024;
   private static final int texSize = 4 * texWidth * texHeight;
   
   private int[] texBufferId = new int[ 1 ];
   private int[] textureId = new int[ 1 ];
   
   private final GLU glu = new GLU();
   
   private transient long lastTimeStamp;
   private transient long ticks;
   
   private float angle;
   
   /**
    * @see javax.media.opengl.GLEventListener#init(javax.media.opengl.GLAutoDrawable)
    */
   public void init( GLAutoDrawable drawable )
   {
      GL gl = drawable.getGL();
      if ( useDebugGL )
      {
         gl = new DebugGL( gl );
         drawable.setGL( gl );
      }
      gl.setSwapInterval( 0 );
      gl.glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
      gl.glShadeModel( GL.GL_FLAT );

      // create texture image buffer object
      gl.glGenBuffers( 1, texBufferId, 0 );

      // Explicitely bind the zero pixel unpack buffer object so that
      // passing NULL for the texture image data leaves the texture image
      // unspecified.
      gl.glBindBuffer( GL.GL_PIXEL_UNPACK_BUFFER_ARB, 0 );

      // create and set 2-D texture
      gl.glGenTextures( 1, textureId, 0 );
      gl.glBindTexture( GL.GL_TEXTURE_2D, textureId[ 0 ] );
      gl.glTexImage2D( GL.GL_TEXTURE_2D, 0, 4, texWidth, texHeight, 0, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, null );
      gl.glTexParameteri( GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST );
      gl.glTexParameteri( GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST );
      gl.glEnable( GL.GL_TEXTURE_2D );
      
      lastTimeStamp = System.currentTimeMillis();
   }

   /**
    * @see javax.media.opengl.GLEventListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int)
    */
   public void reshape( GLAutoDrawable drawable, int x, int y, int width, int height )
   {
      if ( (width == 0) || (height == 0) )
      {
         return;
      }
      
      GL gl = drawable.getGL();
      gl.glViewport( 0, 0, width, height );
      
      gl.glMatrixMode( GL.GL_PROJECTION );
      gl.glLoadIdentity();
      final float w = 1.0f;
      final float h = w * height / width;
      glu.gluOrtho2D( -w, w, -h, h );

      gl.glMatrixMode( GL.GL_MODELVIEW );
      gl.glLoadIdentity();
   }
   
   private static int makeRGBA( byte a, byte b, byte g, byte r )
   {
      return (((a & 0xff) << 24) | ((b & 0xff) << 16) | ((g & 0xff) << 8) | (r & 0xff));
   }
   
   private final ByteBuffer texBuffer = ByteBuffer.allocateDirect( texSize );
   private final IntBuffer texBufferI = texBuffer.asIntBuffer();
   {
      for ( int j = 0; j < texHeight; ++j )
      {
         for ( int i = 0; i < texWidth; ++i )
         {
            final byte value = (byte) ( 128 * (((i >> 6) & 1) ^ ((j >> 6) & 1)) );
            texBufferI.put( makeRGBA( value, value, value, (byte) 0xff ) );
         }
      }
      texBufferI.rewind();
   }

   /**
    * @see javax.media.opengl.GLEventListener#display(javax.media.opengl.GLAutoDrawable)
    */
   public void display( GLAutoDrawable drawable )
   {
      ++ticks;
      final long timeStamp = System.currentTimeMillis();
      final long timeDiff = timeStamp - lastTimeStamp;
      if ( timeDiff > 1000 )
      {
         final float averageTickTimeMilliSeconds = timeDiff / (float) ticks;
         ticks = 0;
         lastTimeStamp = timeStamp;
         System.out.println( "Average frame time: " + averageTickTimeMilliSeconds + "ms" );
      }
      
      GL gl = drawable.getGL();
      
      // bind the texture buffer object
      gl.glBindBuffer( GL.GL_PIXEL_UNPACK_BUFFER_ARB, texBufferId[ 0 ] );
      
      // reset the contents of the texSize-sized buffer object
      gl.glBufferData( GL.GL_PIXEL_UNPACK_BUFFER_ARB, texSize, null, GL.GL_STREAM_DRAW );
      
      // map buffer into client memory, update buffer, and unmap buffer again
      ByteBuffer mappedBuffer = gl.glMapBuffer( GL.GL_PIXEL_UNPACK_BUFFER_ARB, GL.GL_WRITE_ONLY );
      mappedBuffer.put( texBuffer );
      mappedBuffer.rewind();
      texBuffer.rewind();
      gl.glUnmapBuffer( GL.GL_PIXEL_UNPACK_BUFFER_ARB );
      
      // update the texture image, using the currently bound buffer
//      gl.glTexSubImage2D( GL.GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, texBuffer );
      gl.glTexSubImage2D( GL.GL_TEXTURE_2D, 0, 0, 0, texWidth, texHeight, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, 0 );

      gl.glClear( GL.GL_COLOR_BUFFER_BIT );
      
      gl.glPushMatrix();
      gl.glRotatef( angle, 0.0f, 0.0f, 1.0f );
      
      gl.glColor3f( 1.0f, 1.0f, 1.0f );
      
      gl.glBegin( GL.GL_TRIANGLE_STRIP );
      gl.glTexCoord2f( 0.0f, 0.0f );
      gl.glVertex2f( -0.5f, -0.5f );
      gl.glTexCoord2f( 1.0f, 0.0f );
      gl.glVertex2f( 0.5f, -0.5f );
      gl.glTexCoord2f( 0.0f, 1.0f );
      gl.glVertex2f( -0.5f, 0.5f );
      gl.glTexCoord2f( 1.0f, 1.0f );
      gl.glVertex2f( 0.5f, 0.5f );
      gl.glEnd();
      
      gl.glPopMatrix();
      
      gl.glBindBuffer( GL.GL_PIXEL_UNPACK_BUFFER_ARB, 0 );
      
      angle += 0.2f;
   }

   /**
    * @see javax.media.opengl.GLEventListener#displayChanged(javax.media.opengl.GLAutoDrawable, boolean, boolean)
    */
   public void displayChanged( GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged )
   {
      // what to do...
   }
}
