/*
 * $Id$
 */

#include "QGLBitmapWidget.h"

// From GLUT
#if defined(_WIN32)

/* GLUT 3.7 now tries to avoid including <windows.h>
   to avoid name space pollution, but Win32's <GL/gl.h> 
   needs APIENTRY and WINGDIAPI defined properly. */
# if 0
#  define  WIN32_LEAN_AND_MEAN
#  include <windows.h>
# else
   /* XXX This is from Win32's <windef.h> */
#  ifndef APIENTRY
#   define GLUT_APIENTRY_DEFINED
#   if (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#    define APIENTRY    __stdcall
#   else
#    define APIENTRY
#   endif
#  endif
   /* XXX This is from Win32's <winnt.h> */
#  ifndef CALLBACK
#   if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS)
#    define CALLBACK __stdcall
#   else
#    define CALLBACK
#   endif
#  endif
   /* XXX This is from Win32's <wingdi.h> and <winnt.h> */
#  ifndef WINGDIAPI
#   define GLUT_WINGDIAPI_DEFINED
#   define WINGDIAPI __declspec(dllimport)
#  endif
   /* XXX This is from Win32's <ctype.h> */
#  ifndef _WCHAR_T_DEFINED
typedef unsigned short wchar_t;
#   define _WCHAR_T_DEFINED
#  endif
# endif
#endif

#include <GL/gl.h>
#include <assert.h>

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

int
QGLBitmapWidget::roundUpToNextHigherPowerOfTwo( int number )
{
  // I'm sure this can be done without loop with some expression
  for ( int i = 1; i < 31; i++ ) {
    if ( ( 1 << i ) >= number ) {
      return ( 1 << i );
    }
  }
#ifdef _Q_GL_BITMAP_WIDGET_USE_EXCEPTION
  // failure, should never happen
  throw QGLBitmapWidgetError( "Cannot find next higher power of 2" );
#else
  assert("Cannot find next higher power of 2" == 0);
#endif

  // to satisfy the compiler
  return -1;
}

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

QGLBitmapWidget::QGLBitmapWidget( QWidget* parent, const char* name,
  const QGLWidget* shareWidget, WFlags f )
  : QGLWidget( parent, name, shareWidget, f )
{
  frameBufferTexture = 0;
  frameBufferTextureExists = false;
}

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

QGLBitmapWidget::~QGLBitmapWidget()
{
  cleanup();
}

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

void
QGLBitmapWidget::setBufferSize( int argFrameBufferWidth, int argFrameBufferHeight )
{
  frameBufferWidth = argFrameBufferWidth;
  frameBufferHeight = argFrameBufferHeight;

  cleanup();
  glGenTextures( 1, &frameBufferTexture );
  glBindTexture( GL_TEXTURE_2D, frameBufferTexture );

  textureWidth = roundUpToNextHigherPowerOfTwo( frameBufferWidth );
  textureHeight = roundUpToNextHigherPowerOfTwo( frameBufferHeight );

  // test if such texture size is available
  glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, textureWidth,
	       textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );

  int proxyTextureWidth;
  glGetTexLevelParameteriv( GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &proxyTextureWidth );
  if ( proxyTextureWidth == 0 ) {
#ifdef _Q_GL_BITMAP_WIDGET_USE_EXCEPTION    
    throw QGLBitmapWidgetError( "Texture size is not supported by OpenGL" );
#else
    assert("Texture size is not supported by OpenGL" == 0);
#endif
  }

  // NULL texture data is permitted
  glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, textureWidth,
		textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 );

  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
  glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );

  glDisable( GL_CULL_FACE );

  // default is 4, necessary if framebuffer width is not multiple of 4
  glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );

  if ( glGetError() != GL_NO_ERROR ) {
#ifdef _Q_GL_BITMAP_WIDGET_USE_EXCEPTION    
    throw QGLBitmapWidgetError( "OpenGL error in initialization" );
#else
    assert("OpenGL error in initialization" == 0);
#endif    
  }
}

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

void
QGLBitmapWidget::updateFrameBuffer( const unsigned char* frameBuffer )
{
  glBindTexture( GL_TEXTURE_2D, frameBufferTexture );

  glTexSubImage2D( GL_TEXTURE_2D, 0,
		   0, 0,
		   frameBufferWidth, frameBufferHeight,
		   GL_RGBA, GL_UNSIGNED_BYTE, frameBuffer);

  frameBufferTextureExists = true;
}

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

void
QGLBitmapWidget::cleanup()
{
  if ( glIsTexture( frameBufferTexture ) ) {
    glDeleteTextures( 1, &frameBufferTexture );
    frameBufferTexture = 0;
    frameBufferTextureExists = false;
  }
}

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

void
QGLBitmapWidget::resizeGL( int w, int h )
{
  glViewport( 0, 0, static_cast<GLint>( w ), static_cast<GLint>( h ) );
}

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

void
QGLBitmapWidget::paintGL()
{
  if ( frameBufferTextureExists ) {
    // don't try to paint with undefined texture

    glClear( GL_COLOR_BUFFER_BIT );

    glMatrixMode( GL_MODELVIEW );
    glPushMatrix();
    glLoadIdentity();

    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    glLoadIdentity();
    //setting ortographic projection
    glOrtho( 0, width(), height(), 0, -1, 1);

    glBindTexture( GL_TEXTURE_2D, frameBufferTexture );

    glEnable( GL_TEXTURE_2D );
    glDisable( GL_DEPTH_TEST );
    glBindTexture( GL_TEXTURE_2D, frameBufferTexture );
    glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

    glBegin( GL_QUADS );

    glTexCoord2f( 0.0f, static_cast<float>( frameBufferHeight ) / textureHeight);
    glVertex2i( 0, 0 );

    glTexCoord2f(
      static_cast<float>( frameBufferWidth ) / textureWidth,
      static_cast<float>( frameBufferHeight ) / textureHeight
    );
    glVertex2i( width(), 0 );

    glTexCoord2f(
      static_cast<float>( frameBufferWidth ) / textureWidth,
      0.0f
    );
    glVertex2i( width(), height() );

    glTexCoord2f(
      0.0f,
      0.0f
    );
    glVertex2i( 0, height() );

    glEnd();

    glMatrixMode( GL_PROJECTION );
    glPopMatrix();

    glMatrixMode( GL_MODELVIEW );
    glPopMatrix();
  }

  return;
}

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