#include "node_gl.h"
#include "sportcast.h"

#define GL_GLEXT_PROTOTYPES 1
#define GLX_GLXEXT_PROTOTYPES 1

#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <GL/glx.h>
#include <pthread.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void *gl_render_thread_helper( void *s_this )
{
  NodeGL *me = (NodeGL *)s_this;

  me->render_loop();

  return NULL;
}

void GLcheck( const char *where )
{
  GLenum GLerror;
  
  if ( (GLerror = glGetError()) != GL_NO_ERROR ) {
    fprintf( stderr, "GL error (%x) at (%s).\n", GLerror, where );
  }
}

void init_tex( GLint internalformat, GLuint *tex, unsigned int width, unsigned int height )
{
  glGenTextures( 1, tex );
  glBindTexture( GL_TEXTURE_RECTANGLE_ARB, *tex );
  glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0,
		internalformat, width, height,
		0, GL_LUMINANCE, GL_UNSIGNED_BYTE, NULL );

  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 );
}

void init_fb( GLuint *fb, GLuint *tex, unsigned int width, unsigned int height )
{
  init_tex( GL_RGB8, tex, width, height );
  glGenFramebuffersEXT( 1, fb );
  glBindFramebufferEXT( GL_FRAMEBUFFER_EXT, *fb );
  glFramebufferTexture2DEXT( GL_FRAMEBUFFER_EXT,
			     GL_COLOR_ATTACHMENT0_EXT,
			     GL_TEXTURE_RECTANGLE_ARB,
			     *tex, 0 );

  GLuint status
    = glCheckFramebufferStatusEXT( GL_FRAMEBUFFER_EXT );
  if ( status != GL_FRAMEBUFFER_COMPLETE_EXT ) {
    fprintf( stderr, "Framebuffer error (%d, %d) (%x).\n", *fb, *tex, status );
    GLcheck("framebuffer setup");
    exit( 1 );
  }

  GLcheck("after framebuffer setup");
}

NodeGL::NodeGL()
{
  /* Sleep to avoid freezes */
  fprintf( stderr, "Sleeping..." );
  sleep( 10 );
  fprintf( stderr, "done.\n" );
  
  /* Open X connection */
  display = XOpenDisplay( NULL );
  if ( display == NULL ) {
    fprintf( stderr, "Bad DISPLAY (%s).\n", getenv( "DISPLAY" ) );
    exit( 1 );
  }
  
  /* Initialize OpenGL */
  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 );
  }
  
  GLcheck("after glXChooseFBConfig");
  
  /* We don't actually want to use a Pbuffer */
  /* We just want to start OpenGL */
  int pbuffer_attributes[] = {
    GLX_PBUFFER_WIDTH, 0,
    GLX_PBUFFER_HEIGHT, 0,
    0
  };
  
  pbuffer = glXCreatePbuffer( display,
			      configs[ 0 ],
			      pbuffer_attributes );
  if ( !pbuffer ) {
    perror( "glXCreatePbuffer" );
    exit( 1 );
  }
  
  context = glXCreateNewContext( display,
				 configs[ 0 ],
				 GLX_RGBA_TYPE,
				 0,
				 GL_TRUE );
  
  if ( glXMakeCurrent( display, pbuffer, context ) == 0 ) {
    perror( "glXMakeCurrent" );
    exit( 1 );
  }

  GLcheck("after glXMakeCurrent");

  /* initialize framebuffers */
  glEnable( GL_FRAMEBUFFER_EXT );
  glEnable( GL_TEXTURE_RECTANGLE_ARB );
  
  GLcheck("known bug 1");
  
  init_fb( &bg_fb, &bg_tex, WIDTH, HEIGHT );
  
  init_tex( GL_LUMINANCE8, &Y_fg_tex, WIDTH, HEIGHT );
  init_tex( GL_LUMINANCE8, &Cb_fg_tex, WIDTH/2, HEIGHT/2 );
  init_tex( GL_LUMINANCE8, &Cr_fg_tex, WIDTH/2, HEIGHT/2 );

  /* allocate memory */
  uint in_memory_size = (3 * WIDTH * HEIGHT / 2) * (num_cam_frames + num_bg_frames);
  inbound_memory = (char *) glXAllocateMemoryNV( in_memory_size,
						 1.0,
						 0.0,
						 1.0 );
  if ( inbound_memory == NULL ) {
    perror( "glXAllocateMemoryNV" );
    exit( 1 );
  }

  glEnableClientState( GL_READ_PIXEL_DATA_RANGE_NV );
  glPixelDataRangeNV( GL_READ_PIXEL_DATA_RANGE_NV, in_memory_size, inbound_memory );

  outbound_memory = (char *) malloc( (3 * WIDTH * HEIGHT / 2) * num_out_frames );
  if ( outbound_memory == NULL ) {
    perror( "malloc" );
    exit( 1 );
  }

  char *mem_pointer = inbound_memory;
  for ( unsigned int i = 0; i < num_cam_frames; i++ ) {
    cam_frame[ i ][ 0 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT;

    cam_frame[ i ][ 1 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;

    cam_frame[ i ][ 2 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;
  }

  for ( unsigned int i = 0; i < num_bg_frames; i++ ) {
    bg_frame[ i ][ 0 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT;

    bg_frame[ i ][ 1 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;

    bg_frame[ i ][ 2 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;
  }  

  mem_pointer = outbound_memory;
  for ( unsigned int i = 0; i < num_out_frames; i++ ) {
    out_frame[ i ][ 0 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT;

    out_frame[ i ][ 1 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;

    out_frame[ i ][ 2 ] = mem_pointer;
    mem_pointer += WIDTH * HEIGHT / 4;
  }

  /* Set up viewport */
  glViewport( 0, 0, WIDTH, HEIGHT );

  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( 0, WIDTH, 0, HEIGHT, -1, 1 );
  glColor4f( 0.0, 0.0, 0.0, 0.0 );
  glMatrixMode( GL_MODELVIEW );

  GLcheck( "at end of OpenGL setup" );

  next_frame_to_display = 0;

  pthread_create( &thread_handle, NULL, gl_render_thread_helper, this );
}
