//////////////////////////////////////////////////////////////////////////////
//
//  --- PixelTests.cxx ---
//
//////////////////////////////////////////////////////////////////////////////

#include <sys/time.h>

#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>

#include "Params.h"

enum { R = 0, G, B, A };

struct PixelFormat {
  char*   name;
  GLenum  value;
  GLint   alignment;
  double  time;
  GLenum  error;
};

#define EXPAND( value )  #value, value

PixelFormat  formats[] = {
  { EXPAND( GL_BYTE ), 1 },
  { EXPAND( GL_UNSIGNED_BYTE ), 1 },
  { EXPAND( GL_SHORT ), 2 },
  { EXPAND( GL_UNSIGNED_SHORT ), 2 },
  { EXPAND( GL_INT ), 4 },
  { EXPAND( GL_UNSIGNED_INT ), 4 },
  { EXPAND( GL_FLOAT ), 4 },
  { EXPAND( GL_UNSIGNED_SHORT_4_4_4_4 ), 2 },
  { EXPAND( GL_UNSIGNED_SHORT_4_4_4_4_REV ), 2 },
  { EXPAND( GL_UNSIGNED_SHORT_5_5_5_1 ), 2 },
  { EXPAND( GL_UNSIGNED_SHORT_1_5_5_5_REV ), 2 },
  { EXPAND( GL_UNSIGNED_INT_8_8_8_8 ), 4 },
  { EXPAND( GL_UNSIGNED_INT_8_8_8_8_REV ), 4 },
  { EXPAND( GL_UNSIGNED_INT_10_10_10_2 ), 4 }, 
  { EXPAND( GL_UNSIGNED_INT_2_10_10_10_REV ), 4 },
  { EXPAND( GL_ZERO ) }
};

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

template <typename Type> inline Type
convert( int nBits, bool isSigned )
{
  unsigned long long  ones = ~0;

  if ( isSigned )
    --nBits;

  int  i;
  unsigned long long  bits = 0;

  for ( i = 0; i < nBits; ++i )
    bits |= ( 1 << i );

  return (Type) (ones & bits);
}

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

template <typename Type> void
format_1( GLenum format, Type* data, int width, int height )
{
  int   i, j;

  const int NumColors = 5;

  Type  pixels[NumColors];
  Type  r, g, b, a;

  switch( format ) {
    case GL_UNSIGNED_SHORT_4_4_4_4:
      r = convert<GLushort>( 4, false ) << (4*3);
      g = convert<GLushort>( 4, false ) << (4*2);
      b = convert<GLushort>( 4, false ) << (4*1);
      a = convert<GLushort>( 4, false ) << (4*0);
      break;

    case GL_UNSIGNED_SHORT_4_4_4_4_REV:
      r = convert<GLushort>( 4, false ) << (4*0);
      g = convert<GLushort>( 4, false ) << (4*1);
      b = convert<GLushort>( 4, false ) << (4*2);
      a = convert<GLushort>( 4, false ) << (4*3);
      break;

    case GL_UNSIGNED_SHORT_5_5_5_1:
      r = convert<GLshort>( 5, false ) << (5*0);;
      g = convert<GLshort>( 5, false ) << (5*1);;
      b = convert<GLshort>( 5, false ) << (5*2);;
      a = convert<GLshort>( 1, false ) << (5*3);;
      break;

    case GL_UNSIGNED_SHORT_1_5_5_5_REV:
      r = convert<GLushort>( 5, false ) << (5*3+1);
      g = convert<GLushort>( 5, false ) << (5*2+1);
      b = convert<GLushort>( 5, false ) << (5*1+1);
      a = convert<GLushort>( 1, false ) << (5*0+1);
      break;

    case GL_UNSIGNED_INT_8_8_8_8:
      r = convert<GLuint>( 8, false ) << (8*0);
      g = convert<GLuint>( 8, false ) << (8*1);
      b = convert<GLuint>( 8, false ) << (8*2);
      a = convert<GLuint>( 8, false ) << (8*3);
      break;
      
    case GL_UNSIGNED_INT_8_8_8_8_REV:
      r = convert<GLuint>( 8, false ) << (8*3);
      g = convert<GLuint>( 8, false ) << (8*2);
      b = convert<GLuint>( 8, false ) << (8*1);
      a = convert<GLuint>( 8, false ) << (8*0);
      break;
      
    case GL_UNSIGNED_INT_10_10_10_2:
      r = convert<GLuint>( 10, false ) << (10*2+2);
      g = convert<GLuint>( 10, false ) << (10*1+2);
      b = convert<GLuint>( 10, false ) << (10*0+2);
      a = convert<GLuint>( 2, false )  << (10*0);
      break;

    case GL_UNSIGNED_INT_2_10_10_10_REV:
      r = convert<GLuint>( 10, false ) << (10*0);
      g = convert<GLuint>( 10, false ) << (10*0+2);
      b = convert<GLuint>( 10, false ) << (10*1+2);
      a = convert<GLuint>( 2, false )  << (10*2+2);
      break;
  }

  pixels[0] = a;
  pixels[1] = r|a;
  pixels[2] = g|a;
  pixels[3] = b|a;
  pixels[4] = r|g|b|a;

  int  pix = 0, idx = 0;
  for ( j = 0; j < height; ++j ) {
    pix = j % NumColors;
    for ( i = 0; i < width; ++i ) {
      data[idx++] = pixels[pix];
      (++pix) %= NumColors;
    }
  }
}

template <typename Type> void
format_4( GLenum format, Type* data, int width, int height )
{
  const int NumColors = 5;
  Type  pixels[NumColors][4] = {    // Sequence of {Black,Red,Gree,Blue,White}
    { 0, 0, 0, 1 },
    { 1, 0, 0, 1 },
    { 0, 1, 0, 1 },
    { 0, 0, 1, 1 },
    { 1, 1, 1, 1 }
  };

  if ( format != GL_FLOAT ) {
    int   numBits  = 8 * sizeof( Type );
    bool  isSigned = false;
  
    switch( format ) {
      case GL_BYTE:
      case GL_SHORT:
      case GL_INT:
	isSigned = true;
	break;
    }

    Type value = convert<Type>( numBits, isSigned );
    
    pixels[1][R] = value;
    pixels[2][G] = value;
    pixels[3][B] = value;
    pixels[4][R] = value;
    pixels[4][B] = value;
    pixels[4][G] = value;

    pixels[0][A] = value;
    pixels[1][A] = value;
    pixels[2][A] = value;
    pixels[3][A] = value;
    pixels[4][A] = value;
  }

  int  i, j, pix = 0, idx = 0;
  for ( j = 0; j < height; ++j ) {
    pix = j % NumColors;
    for ( i = 0; i < width; ++i ) {
      data[idx++] = pixels[pix][R];
      data[idx++] = pixels[pix][G];
      data[idx++] = pixels[pix][B];
      data[idx++] = pixels[pix][A];
      ++pix;
      pix %= NumColors;
    }
  }
}


#define CASE( Format, Type, CompsPerType ) \
  case (Format): \
    data = (void *) malloc( CompsPerType * sizeof(Type) * width * height ); \
    format_##CompsPerType( Format, (Type *) data, width, height ); \
    break

void*
genData( GLenum format, int width, int height )
{
  void*  data;

  switch ( format ) {
    CASE( GL_BYTE, GLbyte, 4 );
    CASE( GL_UNSIGNED_BYTE, GLubyte, 4 );
    CASE( GL_SHORT, GLshort, 4 );
    CASE( GL_UNSIGNED_SHORT, GLushort, 4 );
    CASE( GL_INT, GLint, 4 );
    CASE( GL_UNSIGNED_INT, GLuint, 4 );
    CASE( GL_FLOAT, GLfloat, 4 );
    CASE( GL_UNSIGNED_SHORT_4_4_4_4, GLushort, 1 );
    CASE( GL_UNSIGNED_SHORT_4_4_4_4_REV, GLushort, 1 );
    CASE( GL_UNSIGNED_SHORT_5_5_5_1, GLushort, 1 );
    CASE( GL_UNSIGNED_SHORT_1_5_5_5_REV, GLushort, 1 );
    CASE( GL_UNSIGNED_INT_8_8_8_8, GLuint, 1 );
    CASE( GL_UNSIGNED_INT_8_8_8_8_REV, GLuint, 1 );
    CASE( GL_UNSIGNED_INT_10_10_10_2, GLuint, 1 );
    CASE( GL_UNSIGNED_INT_2_10_10_10_REV, GLuint, 1 );
  }

  return data;
}

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

void
runTests( int width, int height )
{
  int     i;
  void*   pixels;
  PixelFormat* format;
  struct timeval  start, finish;

  //
  //  Initialize OpenGL state
  //

  glRasterPos2i( 0, 0 );
    
  for ( format = formats; format->value != GL_ZERO; ++format ) {

    format->error = GL_NO_ERROR;

    pixels = genData( format->value, width, height );

    glPixelStorei( GL_PACK_ALIGNMENT, format->alignment );
    
    glClear( GL_COLOR_BUFFER_BIT );
    glDrawPixels( width, height, GL_RGBA, format->value, pixels );
  
    gettimeofday( &start, NULL );
    for ( i = 0; i < numTrials; ++i )
      glDrawPixels( width, height, GL_RGBA, format->value, pixels );    
    glFinish();
    gettimeofday( &finish, NULL );

    format->time = finish.tv_usec - start.tv_usec;
    if ( format->time < 0 ) {
      --finish.tv_sec;
      format->time += 1000000;
    }
    format->time /= 1000000;
    format->time += (finish.tv_sec - start.tv_sec);
    
    GLenum error;
    while ( (error = glGetError()) != GL_NO_ERROR ) {
      format->time = -1.0;
      format->error = error;
    }

    free( pixels );
  }

  double  minTime = formats[0].time;
  for ( format = &formats[1]; format->value != GL_ZERO; ++format )
    if ( format->time > 0 && minTime > format->time )
      minTime = format->time;

  for ( format = formats; format->value != GL_ZERO; ++format ) {
    printf( "  %-35s: ", format->name );

    if ( format->time > 0 )
      printf( "%6f secs [ % 7.3f ]\n", format->time, format->time / minTime );
    else
      printf( "Returned '%s' error\n", gluErrorString(format->error) );
  }
}

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

