
#include <sys/time.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <GL/glut.h>

#include "Params.h"
#include "RenderFuncs.h"

char* progname;

bool  printSummaryOnly = false;
bool  exitWhenDone = true;
bool  colorMaterial = false;
bool  wireframeMode = false;
bool  depthBuffer = true;

const float ZERO_EPSILION = 1.0E-6;

//----------------------------------------------------------------------------

void
usage( bool helpMode = true )
{
  printf( "\nUsage: %s [-hskmwtzv] [-f #] [-p #] [-n #] [-g w h] [-CNT]\n"
	  "\n"
	  "  -h   : help (show this message)\n"
	  "  -C   : pass colors as well as vertices\n"
	  "  -d   : disable double-buffering\n"
	  "  -f # : render '#' number of frames per mode (default 20)\n"
	  "  -g   : set window dimensions to (w,h)\n"
	  "  -k   : don't exit after test is complete\n"
	  "  -m   : enable GL_COLOR_MATERIAL mode\n"
	  "  -n # : render '#' of primitives per frame\n"
	  "  -N   : pass normals as well as vertices (enables lighting)\n"
	  "  -p # : call the rendering function '#' times (default 1)\n"
	  "  -s   : print only summary statistics\n"
	  "  -t   : use GL_TRINGLE_STRIP as compared to GL_TRIANGLES\n"
	  "  -T   : pass texture coordinates as well as vertices"
	  " (enables texturing\n"
	  "  -v   : print command line\n"
	  "  -w   : use glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );\n"
	  "  -z   : disable depth testing (z-buffering)\n"
	  "\n",
	  progname );

  exit( helpMode ? EXIT_SUCCESS : EXIT_FAILURE );
}

//----------------------------------------------------------------------------

inline float
uniformRand( void )
{
  return (2 * drand48() - 1.0);
}

inline void
crossProduct( GLfloat* r, GLfloat* n )
{
  GLfloat  u[3], v[3];

  u[0] = r[3] - r[0];
  u[1] = r[4] - r[1];
  u[2] = r[5] - r[2];

  v[0] = r[6] - r[0];
  v[1] = r[7] - r[1];
  v[2] = r[8] - r[2];

  n[0] = u[1]*v[2] - u[2]*v[1];
  n[1] = u[2]*v[0] - u[0]*v[2];
  n[2] = u[0]*v[1] - u[1]*v[0];

  double len = sqrt( n[0]*n[0] + n[1]*n[1] + n[2]*n[2] );

  if ( len > ZERO_EPSILION )
    len = 1.0 / len;
  
  n[0] *= len;
  n[1] *= len;
  n[2] *= len;
}

void
init( void )
{
  int  i;
  int  size;

  switch ( primType ) {
    case GL_TRIANGLES:
      numVertices = 3 * numPrimitives;
      break;

    case GL_TRIANGLE_STRIP:
      numVertices = numPrimitives + 2;
      break;
  }

  indices = new GLuint[numVertices];
  for ( i = 0; i < numVertices; ++i )
    indices[i] = i;
  
  size = 3 * numVertices;
  
  vertexData = new GLfloat[size];
  for ( i = 0; i < size; ++i )
    vertexData[i] = 0.9 * maxCoordValue * uniformRand();

  if ( renderMode & Color ) {
    int colorSize = size;

    if ( renderMode & Normal )
      colorSize += numVertices;

    colorData = new GLfloat[colorSize];

    for ( i = 0; i < colorSize; ) {
      colorData[i++] = drand48();
      colorData[i++] = drand48();
      colorData[i++] = drand48();
      if ( renderMode & Normal )
	colorData[i++] = 1.0;
    }
  } else
    colorData = (GLfloat *) NULL;

  if ( renderMode & Normal ) {
    normalData = new GLfloat[size];

    for ( i = 0; i < numVertices; ) {
      GLfloat  n[3];

      crossProduct( &vertexData[i], n );
      normalData[i++] = n[0];
      normalData[i++] = n[1];
      normalData[i++] = n[2];
      if ( primType == GL_TRIANGLE_STRIP ) {
	normalData[i++] = n[0];
	normalData[i++] = n[1];
	normalData[i++] = n[2];
	normalData[i++] = n[0];
	normalData[i++] = n[1];
	normalData[i++] = n[2];
      }
    }
  } else
    normalData = (GLfloat *) NULL;

  if ( renderMode & TexCoord ) {
    int texCoordSize = 2 * numVertices;

    texCoordData = new GLfloat[texCoordSize];

    for ( i = 0; i < texCoordSize; ) {
      texCoordData[i++] = drand48();
      texCoordData[i++] = drand48();
    } 
  } else
    texCoordData = (GLfloat *) NULL;

  int  interleavedSize[] = { 3, 6, 6, 10, 5, 8, 8, 12 };

  size = interleavedSize[renderMode] * numVertices;

  interleavedData = new GLfloat[size];

  GLfloat*  ilTmp = interleavedData;
  for ( i = 0; i < numVertices; ++i ) {
    int  idx = 3 * i;

    if ( renderMode & TexCoord ) {
      *ilTmp++ = texCoordData[idx+0];
      *ilTmp++ = texCoordData[idx+1];
    }
    
    if ( renderMode & Color ) {
      *ilTmp++ = colorData[idx+0];
      *ilTmp++ = colorData[idx+1];
      *ilTmp++ = colorData[idx+2];
      if ( renderMode & Normal )
	*ilTmp++ = colorData[idx+3];
    }

    if ( renderMode & Normal ) {
      *ilTmp++ = normalData[idx+0];
      *ilTmp++ = normalData[idx+1];
      *ilTmp++ = normalData[idx+2];
    }

    *ilTmp++ = vertexData[idx+0];
    *ilTmp++ = vertexData[idx+1];
    *ilTmp++ = vertexData[idx+2];
  }

  //
  // Set up OpenGL state
  //

  if ( depthBuffer )
    glEnable( GL_DEPTH_TEST );

  if ( wireframeMode )
    glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

  glVertexPointer( 3, GL_FLOAT, 0, vertexData );

  if ( renderMode & Color ) {
    GLint  size;
    if ( renderMode & Normal )
      size = 4;
    else
      size = 3;

    glColorPointer( size, GL_FLOAT, 0, colorData );
  }

  if ( renderMode & Normal ) {
    glNormalPointer( GL_FLOAT, 0, normalData );

    glEnable( GL_LIGHT0 );
    glEnable( GL_LIGHTING );

    if ( colorMaterial ) {
      glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE );
      glEnable( GL_COLOR_MATERIAL );
    }
  }

  if ( renderMode & TexCoord ) {
    glTexCoordPointer( 2, GL_FLOAT, 0, texCoordData );

    GLfloat  tex[4] = { 1.0, 0.0, 0.0, 1.0 };

    glTexImage2D( GL_TEXTURE_2D, 0, GL_LUMINANCE, 2, 2, 0,
		  GL_LUMINANCE,GL_FLOAT, tex );

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    
    glEnable( GL_TEXTURE_2D );
  }
}

//----------------------------------------------------------------------------

void
display( void )
{
  int             i;
  int             method;
  double          time, average[NumMethods], tris[NumMethods];
  struct timeval  start, end;

  GLbitfield      clearMask = GL_COLOR_BUFFER_BIT;

  if ( depthBuffer )
    clearMask |= GL_DEPTH_BUFFER_BIT;

  for ( method = Immediate; method < NumMethods; ++method ) {
    if ( !printSummaryOnly )
      printf( "\nUsing %s method\n\n", methodName[method] );

    glClearColor( clear[method].r, clear[method].g,
		  clear[method].b, clear[method].a );
    
    if ( setup[method] )
      setup[method]();

    average[method] = 0.0;
    
    for ( i = 0; i < numFrames; ++i ) {
      glClear( clearMask );
      gettimeofday( &start, NULL );
      render[method]();
      glFinish();
      gettimeofday( &end, NULL );
      glutSwapBuffers();
    
      //  Compute this frame's time
      time = end.tv_usec - start.tv_usec;
      if ( time < 0 ) {
	--end.tv_sec;
	time += 1000000;
      }
      time /= 1000000;
      time += end.tv_sec - start.tv_sec;

      if ( !printSummaryOnly )
	printf( "  Frame % 3d: %f seconds to render %d triangles "
		"[ %.2f tris/sec ]\n",
		i+1, time, numPrimitives, numPrimitives / time );

      average[method] += time;
    }

    if ( finish[method] )
      finish[method]();

    average[method] /= numFrames;
    tris[method] = numPrimitives / average[method];

    if ( !printSummaryOnly )
      printf( "\n  Average time to render %d frames: %f seconds"
	      " [ %.2f tris/sec ]\n",
	      numFrames, average[method], tris[method] );
  }

  printf( "\n" );
  printf( "  Render Method           Secs/frame"
	  "        Quads/second     Ratio\n" );
  printf( "  -------------           ----------"
	  "        ------------    --------\n\n" );

  double minTime = average[Immediate];
  for ( method = Immediate+1; method < NumMethods; ++method )
    if ( minTime > average[method] )
      minTime = average[method];
  
  for ( method = Immediate; method < NumMethods; ++method )
    printf( "  %- 24s %f       % 13.2f      % 6.3f\n", methodName[method],
	    average[method], tris[method], average[method] / minTime );
  printf( "\n" );

  if ( exitWhenDone )
    exit( EXIT_SUCCESS );
}

//----------------------------------------------------------------------------

void
reshape( int width, int height )
{
  GLdouble xMax = maxCoordValue, xMin = -xMax;
  GLdouble yMax = maxCoordValue, yMin = -yMax;

  glViewport( 0, 0, width, height );

  GLdouble  aspect = (GLdouble) width / height;
  if ( aspect > 0 ) {
    xMin *= aspect;
    xMax *= aspect;
  } else {
    yMin /= aspect;
    yMax /= aspect;
  }
  glMatrixMode( GL_PROJECTION );
  glLoadIdentity();
  glOrtho( xMin, xMax, yMin, yMax, nearPlane, farPlane );
  glMatrixMode( GL_MODELVIEW );
}

//----------------------------------------------------------------------------

void
keyboard( unsigned char key, int x, int y )
{
  switch( key ) {
    case 033:
      exit( EXIT_SUCCESS );
      break;
  }
}

//----------------------------------------------------------------------------

void
main( int argc, char** argv )
{
  int           i;
  unsigned int  displayMask = GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE;
  int           width = 512, height = 512;

  progname = argv[0];

  numPasses = 1;
  numFrames = 20;
  numPrimitives = 10000;

  nearPlane = -maxCoordValue;
  farPlane  =  maxCoordValue;
  
  renderMode = Vertex;
  primType = GL_TRIANGLES;

  for ( i = 1; i < argc; ++i ) {
    char*  tmp = argv[i];

    if ( *tmp != '-' )
      usage( false );

    for ( ++tmp; *tmp; ++tmp ) 
      switch( *tmp ) {
	case 'f':
	  numFrames = atoi( argv[++i] );
	  break;

	case 'p':
	  numPasses = atoi( argv[++i] );
	  break;

	case 'n':
	  numPrimitives = atoi( argv[++i] );
	  break;

	case 'g':
	  width = atoi( argv[++i] );
	  height = atoi( argv[++i] );
	  break;
	  
	case 'C':
	  renderMode |= Color;
	  break;

	case 'N':
	  renderMode |= Normal;
	  break;

	case 'T':
	  renderMode |= TexCoord;
	  break;

	case 'd':
	  displayMask ^= GLUT_DOUBLE;
	  break;

	case 's':
	  printSummaryOnly = true;
	  break;

	case 'k':
	  exitWhenDone = false;
	  break;

	case 'm':
	  colorMaterial = true;
	  break;

	case 't':
	  primType = GL_TRIANGLE_STRIP;
	  break;

	case 'w':
	  wireframeMode = true;
	  break;

	case 'z':
	  depthBuffer = false;
	  break;

	case 'v': {
	  int  arg;
	  for ( arg = 0; arg < argc; ++arg )
	    printf( "%s ", argv[arg] );
	  printf( "\n" );
	} break;
	  
	default:
	  usage( *tmp == 'h' );
	
      }
  }
  
  glutInit( &argc, argv );
  glutInitDisplayMode( displayMask );
  glutInitWindowSize( width, height );
  glutInitWindowPosition( 100, 100 );
  glutCreateWindow( progname );
  
  init();

  glutDisplayFunc( display );
  glutReshapeFunc( reshape );
  glutKeyboardFunc( keyboard );

  glutMainLoop();
}
