#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>

#include <sys/time.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <X11/Xlib.h>

#define GL_GLEXT_PROTOTYPES 1

#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>

#define FILENAME_A "sportcast-1.rgb"
#define FILENAME_B "sportcast-2.rgb"
#define OUTPUT     "output.rgb"
#define FILE_WIDTH 1280
#define FILE_HEIGHT 720

#define WIDTH 1280
#define HEIGHT 720

void drawrect( double angle, double size )
{
  glLoadIdentity();
  glTranslatef( 1280/2, 720/2, 0 );
  glRotatef( angle, 0, 0, 1 );
  
  glBegin( GL_POLYGON );
  glTexCoord2s( 0, 0 );
  glVertex2f( -WIDTH * size, -HEIGHT * size );
  glTexCoord2s( FILE_WIDTH, 0 );
  glVertex2f( WIDTH * size, -HEIGHT * size );
  glTexCoord2s( FILE_WIDTH, FILE_HEIGHT );
  glVertex2f( WIDTH * size, HEIGHT * size );
  glTexCoord2s( 0, FILE_HEIGHT );
  glVertex2f( -WIDTH * size, HEIGHT * size );
  glEnd();
}

int main( int argc, char *argv[] )
{
  Display *display = XOpenDisplay( NULL );
  if ( display == NULL ) {
    fprintf( stderr, "Bad DISPLAY (%s).\n", getenv( "DISPLAY" ) );
    exit( 1 );
  }

  int attributes[] = {
    GLX_RED_SIZE, 8,
    GLX_GREEN_SIZE, 8,
    GLX_BLUE_SIZE, 8,
    GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT,
    0
  };

  int nconfigs = 0;
  GLXFBConfig *configs = glXChooseFBConfig( display,
					    DefaultScreen( display ),
					    attributes,
					    &nconfigs );
  if ( !configs ) {
    fprintf( stderr, "Num configs: %d.\n", nconfigs );
    exit( 1 );
  }

  int pbuffer_attributes[] = {
    GLX_PBUFFER_WIDTH, WIDTH,
    GLX_PBUFFER_HEIGHT, HEIGHT,
    0
  };

  GLXPbuffer pbuffer = glXCreatePbuffer( display,
					 configs[ 0 ],
					 pbuffer_attributes );
  if ( !pbuffer ) {
    perror( "glXCreatePbuffer" );
    exit( 1 );
  }

  GLXContext context = glXCreateNewContext( display,
					    configs[ 0 ],
					    GLX_RGBA_TYPE,
					    0,
					    GL_TRUE );

  if ( glXMakeCurrent( display, pbuffer, context ) == 0 ) {
    perror( "glXMakeCurrent" );
    exit( 1 );
  }

  glEnable( GL_TEXTURE_RECTANGLE_ARB );
  glEnable( GL_FRAMEBUFFER_EXT );
  
  GLuint fb = 0;
  glGenFramebuffersEXT( 1, &fb );

  GLuint my_texture = 0;
  glGenTextures( 1, &my_texture );
  glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fb );

  glBindTexture( GL_TEXTURE_RECTANGLE_ARB, my_texture );
  glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0,
		GL_RGB8, FILE_WIDTH, FILE_HEIGHT, 0,
		GL_RGB, GL_UNSIGNED_BYTE, NULL );

  glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT,
			     GL_COLOR_ATTACHMENT0_EXT,
			     GL_TEXTURE_RECTANGLE_ARB,
			     my_texture, 0 );

  GLenum status = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
  if ( status != GL_FRAMEBUFFER_COMPLETE_EXT ) {
    fprintf( stderr, "Framebuffer status (%d, %d): 0x%X\n", fb, my_texture,
	     status );
    //    exit( 1 );
  }

  /*
  fprintf( stderr, "Switch out of graphics mode now.\n" );
  sleep( 5 );
  fprintf( stderr, "Rendering...\n" );
  */

  glViewport( 0, 0, WIDTH, HEIGHT );
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( 0, WIDTH, 0, HEIGHT, -1, 1 );
  glColor3f( 0.0, 1.0, 0.3 );
  
  glMatrixMode( GL_MODELVIEW );
  glLoadIdentity();

  /* open the three files */
  int pica_fd = open( FILENAME_A, O_RDONLY );
  if ( pica_fd < 0 ) {
    perror( "open" );
    exit( 1 );
  }
  
  int picb_fd = open( FILENAME_B, O_RDONLY );
  if ( picb_fd < 0 ) {
    perror( "open" );
    exit( 1 );
  }

  int output_fd = open( OUTPUT, O_RDWR  );
  if ( output_fd < 0 ) {
    perror( "open" );
    exit( 1 );
  }

  /* mmap the three files */
  void *file_adata = mmap( 0,
			      FILE_WIDTH * FILE_HEIGHT * 3,
			      PROT_READ,
			      MAP_SHARED,
			      pica_fd,
			      0 );
  if ( file_adata == NULL ) {
    perror( "mmap" );
    exit( 1 );
  }
  
  void *file_bdata = mmap( 0,
			      FILE_WIDTH * FILE_HEIGHT * 3,
			      PROT_READ,
			      MAP_SHARED,
			      picb_fd,
			      0 );
  if ( file_bdata == NULL ) {
    perror( "mmap" );
    exit( 1 );
  }

  void *rendered_image = mmap( 0,
			       WIDTH * HEIGHT * 3,
			       PROT_READ | PROT_WRITE,
			       MAP_SHARED,
			       output_fd,
			       0 );
  if ( rendered_image == NULL ) {
    perror( "mmap" );
    exit( 1 );
  }

  /* copy input files to memory */
  char *texturea_data = (char *) malloc( FILE_WIDTH * FILE_HEIGHT * 3 );
  memcpy( texturea_data, file_adata, FILE_WIDTH * FILE_HEIGHT * 3 );
  char *textureb_data = (char *) malloc( FILE_WIDTH * FILE_HEIGHT * 3 );
  memcpy( textureb_data, file_bdata, FILE_WIDTH * FILE_HEIGHT * 3 );
  char *my_output = (char *) malloc( WIDTH * HEIGHT * 3 );

  /* set texture parameters */
  glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S,
		   GL_CLAMP_TO_EDGE );
  glTexParameteri( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T,
		   GL_CLAMP_TO_EDGE );
  
  glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
  
  struct timeval last_frame;
  struct timeval this_frame;
  
  gettimeofday( &last_frame, NULL );
  
  int frame = 0;
  float angle = 0;

  glRasterPos2s( 0, 0 );

  glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
  glBindTexture( GL_TEXTURE_RECTANGLE_ARB, my_texture );
  
  while ( 1 ) {
    glBindTexture( GL_TEXTURE_RECTANGLE_ARB, 0 );
    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fb );

    glDrawPixels( FILE_WIDTH, FILE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
		  texturea_data );

    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
    glBindTexture( GL_TEXTURE_RECTANGLE_ARB, my_texture );

    /*
    glTexSubImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0,
		     FILE_WIDTH, FILE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
		     texturea_data );
    */

    drawrect( 35.0, 1.0 );

    glBindTexture( GL_TEXTURE_RECTANGLE_ARB, 0 );
    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, fb );

    glDrawPixels( FILE_WIDTH, FILE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
		  textureb_data );

    glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, 0 );
    glBindTexture( GL_TEXTURE_RECTANGLE_ARB, my_texture );

    /*
    glTexSubImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0,
		     FILE_WIDTH, FILE_HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
		     textureb_data );
    */

    //    drawrect( 135.0, 0.25 );

    glReadPixels( 0, 0, WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE,
    		  rendered_image );

    frame++;
    if ( frame % 100 == 0 ) {
      gettimeofday( &this_frame, NULL );
      int microseconds = 1000000 * ( this_frame.tv_sec - last_frame.tv_sec )
	+ ( this_frame.tv_usec - last_frame.tv_usec );
      double fps = (1000000.0 * 100) / microseconds;
      fprintf( stderr, "Frames per second: %f.\n", fps );
      last_frame.tv_sec = this_frame.tv_sec;
      last_frame.tv_usec = this_frame.tv_usec;
    }

    if ( frame > 1000 ) {
      break;
    }
    
  }

  return 0;
}
