
#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  cullBackfaces = false;
bool  wireframeMode = false;
bool  rotate = true;

GLbitfield clearMask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;

GLfloat  angle = 0.0;

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

void
usage( bool helpMode = true )
{
  printf( "\nUsage: %s [-hagbdzswkv] [-f #] [-p #] [-n #] [-g w h]\n"
	  "\n"
	  "  -h   : help (show this message)\n"
	  "  -a   : disable rotation\n"
	  "  -b   : enable back-fack culling\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"
	  "  -n # : render '#' of cubes per frame\n"
	  "  -p # : call the rendering function '#' times (default 1)\n"
	  "  -s   : print only summary statistics\n"
	  "  -v   : print out 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);
}


void
init( void )
{
  int  i, j;
  int  size = 3 * numPrimitives;

  //
  //  Initialize data
  //

  colorData = new GLfloat[size];
  vertexData = new GLfloat[NUM_CUBE_VERTICES*size];

  float  coordRange = 0.9 * maxCoordValue / sqrt(3);

  GLfloat*  c = colorData;
  GLfloat*  v = vertexData;
  
  for ( i = 0; i < numPrimitives; ++i ) {
    *c++ = drand48();
    *c++ = drand48();
    *c++ = drand48();

    GLfloat  center[3], scale[3];

    center[X] = coordRange * uniformRand();
    center[Y] = coordRange * uniformRand();
    center[Z] = coordRange * uniformRand();

    scale[X] = 0.5 * drand48() + 0.25;
    scale[Y] = 0.5 * drand48() + 0.25;
    scale[Z] = 0.5 * drand48() + 0.25;

    for ( j = 0; j < NUM_CUBE_VERTICES; ++j ) {
      *v++ = scale[X] * cubeVertices[j][X] + center[X];
      *v++ = scale[Y] * cubeVertices[j][Y] + center[Y];
      *v++ = scale[Z] * cubeVertices[j][Z] + center[Z];
    }
  }

  //
  // Set up OpenGL state
  //

  if ( clearMask & GL_DEPTH_BUFFER_BIT )
    glEnable( GL_DEPTH_TEST );

  if ( wireframeMode )
    glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

  if ( cullBackfaces )
    glEnable( GL_CULL_FACE );
}

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

void
display( void )
{
  int             i;
  int             method;
  int             numQuads = NUM_CUBE_FACES * numPrimitives;
  double          time, average[NumMethods], quads[NumMethods];
  struct timeval  start, end;

  for ( method = Technique1; 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 );
    
    average[method] = 0.0;

    angle = 0.0;

    glClear( clearMask );
    render[method]();
    
    for ( i = 0; i < numFrames; ++i ) {
      glClear( clearMask );
      if ( rotate ) {
	glPushMatrix();
	angle += 2.0;
	glRotatef( angle, 1, 1, 1 );
      }
      gettimeofday( &start, NULL );
      render[method]();
      glFinish();
      gettimeofday( &end, NULL );
      if ( rotate )
	glPopMatrix();
      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 cubes "
		"[ %.2f quads/sec ]\n",
		i+1, time, numPrimitives, numQuads / time );

      average[method] += time;
    }

    average[method] /= numFrames;
    quads[method] = numQuads / average[method];

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

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

  double minTime = average[Technique1];
  for ( method = Technique2; method < NumMethods; ++method )
    if ( minTime > average[method] )
      minTime = average[method];
  
  for ( method = Technique1; method < NumMethods; ++method )
    printf( "  %- 24s %f           %.2f      % 6.3f\n", methodName[method],
	    average[method], quads[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 > 1.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;

//     case 't':
//       angle = 0.0;
//       technique = (++technique % NUM_TECHNIQUES );
//       printf( "Using drawing function %d\n", technique );
//       break;
  }
}

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

void
idle( void )
{
//   if ( ++count >= numFrmaes ) {
//     if ( cycle && numCycles > 0 ) {
//       count = 0;
//       angle = 0.0;
//       technique = (++technique % NUM_TECHNIQUES );
//       printf( "Using drawing function %d\n", technique );
//       if ( technique == (NUM_TECHNIQUES - 1) )
// 	--numCycles;
//     } else {
//       exit( EXIT_SUCCESS );
//     }
//   }

  glutPostRedisplay();
}

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

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;

  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 'a':
	  rotate = false;
	  break;

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

	case 's':
	  printSummaryOnly = true;
	  break;

	case 'k':
	  exitWhenDone = false;
	  break;

	case 'w':
	  wireframeMode = true;
	  break;

	case 'z':
	  clearMask ^= GL_DEPTH_BUFFER_BIT;
	  displayMask ^= GLUT_DEPTH;
	  break;

	case 'b':
	  cullBackfaces = true;
	  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 );
  if ( numFrames > 0 ) {
    glutIdleFunc( idle );
  } else {
    glutIdleFunc( glutPostRedisplay );
    glutKeyboardFunc( keyboard );
  }

  glutMainLoop();
}
