
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include "wglext.h"
#include "glext.h"
#include "PajaTypes.h"
#include "PBufferC.h"


using namespace PajaTypes;

static
void
TRACE( const char* szFormat, ...  )
{
	char	szMsg[256];
	va_list	rList;
	va_start( rList, szFormat );
	_vsnprintf( szMsg, 255, szFormat, rList );
	va_end( rList );
	OutputDebugString( szMsg );
}


static
bool
CHECK_GL_ERROR( const char* szName )
{
	GLenum	eError = glGetError();
	if( eError ) {
		OutputDebugString( szName );
		OutputDebugString( " " );
		OutputDebugString( (const char*)gluErrorString( eError ) );
		OutputDebugString( "\n" );
		return true;
	}
	return false;
}

static bool g_bPBufferExtInit = false;

// pbuffer function prototypes
//static PFNWGLMAKECONTEXTCURRENTARBPROC		wglMakeContextCurrentARB = NULL;
static PFNWGLCHOOSEPIXELFORMATARBPROC		wglChoosePixelFormatARB = NULL;
static PFNWGLCREATEPBUFFERARBPROC			wglCreatePbufferARB = NULL;
static PFNWGLDESTROYPBUFFERARBPROC			wglDestroyPbufferARB = NULL;
static PFNWGLGETPBUFFERDCARBPROC			wglGetPbufferDCARB = NULL;
static PFNWGLRELEASEPBUFFERDCARBPROC		wglReleasePbufferDCARB = NULL;
static PFNWGLQUERYPBUFFERARBPROC			wglQueryPbufferARB = NULL;
static PFNWGLGETPIXELFORMATATTRIBIVARBPROC	wglGetPixelFormatAttribivARB = NULL;

//typedef const char * (WINAPI * PFNWGLGETEXTENSIONSSTRINGARBPROC) (HDC hdc);
static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL;

static bool g_bPBufferBindTexInit = false;


static
bool
init_extensions()
{
	if( g_bPBufferExtInit )
		return true;

	HDC	hDC = wglGetCurrentDC();

	// initialize extension entry points
	wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress( "wglChoosePixelFormatARB" );
	wglCreatePbufferARB = (PFNWGLCREATEPBUFFERARBPROC)wglGetProcAddress( "wglCreatePbufferARB" );
	wglDestroyPbufferARB = (PFNWGLDESTROYPBUFFERARBPROC)wglGetProcAddress( "wglDestroyPbufferARB" );
	wglGetPbufferDCARB = (PFNWGLGETPBUFFERDCARBPROC)wglGetProcAddress( "wglGetPbufferDCARB" );
	wglReleasePbufferDCARB = (PFNWGLRELEASEPBUFFERDCARBPROC)wglGetProcAddress( "wglReleasePbufferDCARB" );
	wglQueryPbufferARB = (PFNWGLQUERYPBUFFERARBPROC)wglGetProcAddress( "wglQueryPbufferARB" );
	wglGetPixelFormatAttribivARB = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)wglGetProcAddress( "wglGetPixelFormatAttribivARB" );

	wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)wglGetProcAddress( "wglGetExtensionsStringARB" );

	if( wglGetExtensionsStringARB )
		const char *buf = wglGetExtensionsStringARB( hDC );
	if( wglChoosePixelFormatARB == NULL )
		return false;
	if( wglCreatePbufferARB == NULL )
		return false;
	if( wglDestroyPbufferARB == NULL )
		return false;
	if( wglGetPbufferDCARB == NULL )
		return false;
	if( wglReleasePbufferDCARB == NULL )
		return false;
	if( wglQueryPbufferARB == NULL )
		return false;
	if( wglGetPixelFormatAttribivARB == NULL )
		return false;
	g_bPBufferExtInit = true;

	return true;
}



PBufferC::PBufferC() :
	m_hPBuffer( 0 ),
	m_hPBufferDC( 0 ),
	m_hPBufferRC( 0 ),
	m_ui32PBufferTexID( 0 ),
	m_ui32Width( 0 ),
	m_ui32Height( 0 ),
	m_ui32Mode( 0 )
{
	// empty
}

PBufferC::~PBufferC()
{
	destroy();
}

#define MAX_PFORMATS 256
#define MAX_ATTRIBS  32

bool
PBufferC::init( uint32 ui32Width, uint32 ui32Height, uint32 ui32Mode )
{
	// if extensions cannot be initialsed, bail out.
	if( !init_extensions() )
		return false;

	HDC		hOldDC = wglGetCurrentDC();
	HGLRC	hOldRC = wglGetCurrentContext();

	TRACE( "DC = 0x%x  RC = 0x%x\n", hOldDC, hOldRC );

	// destroy current buffer
	destroy();

	// Query for a suitable pixel format based on the specified mode.
	int32		iAttributes[2 * MAX_ATTRIBS];
	float32 	fAttributes[2 * MAX_ATTRIBS];
	int32		nFAttribs = 0;
	int32		nIAttribs = 0;
	
	// Attribute arrays must be "0" terminated - for simplicity, first
	// just zero-out the array entire, then fill from left to right.
	for( int32 a = 0; a < 2 * MAX_ATTRIBS; a++ ) {
		iAttributes[a] = 0;
		fAttributes[a] = 0;
	}
	
	// Since we are trying to create a pbuffer, the pixel format we
	// request (and subsequently use) must be "p-buffer capable".
	iAttributes[2*nIAttribs  ] = WGL_DRAW_TO_PBUFFER_ARB;
	iAttributes[2*nIAttribs+1] = true;
	nIAttribs++;
	
	iAttributes[2*nIAttribs  ] = WGL_PIXEL_TYPE_ARB;
	iAttributes[2*nIAttribs+1] = WGL_TYPE_RGBA_ARB;
	nIAttribs++;
	
	iAttributes[2*nIAttribs  ] = WGL_ALPHA_BITS_ARB;
	iAttributes[2*nIAttribs+1] = 8;
	nIAttribs++;

	iAttributes[2*nIAttribs  ] = WGL_DEPTH_BITS_ARB;
	iAttributes[2*nIAttribs+1] = 1;
	nIAttribs++;
	
	iAttributes[2*nIAttribs  ] = WGL_STENCIL_BITS_ARB;
	iAttributes[2*nIAttribs+1] = 1;
	nIAttribs++;
	
	iAttributes[2*nIAttribs  ] = WGL_SUPPORT_OPENGL_ARB;
	iAttributes[2*nIAttribs+1] = true;
	nIAttribs++;
	
	int format;
	int pformat[MAX_PFORMATS];
	unsigned int nformats;
	if( !wglChoosePixelFormatARB( hOldDC, iAttributes, fAttributes, MAX_PFORMATS, pformat, &nformats ) ) {
		wglMakeCurrent( hOldDC, hOldRC );
		TRACE( "init failed: wglChoosePixelFormatARB\n" );
		return false;
	}
	format = pformat[0];
	

	// Create the p-buffer.
	iAttributes[0] = 0;
	m_hPBuffer = wglCreatePbufferARB( hOldDC, format, ui32Width, ui32Height, iAttributes );
	if ( !m_hPBuffer ) {
		DWORD err = GetLastError();
		TRACE( "pbuffer creation error:  wglCreatePbufferARB() failed\n" );
		if ( err == ERROR_INVALID_PIXEL_FORMAT ) {
			TRACE( "error:  ERROR_INVALID_PIXEL_FORMAT\n" );
		}
		else if ( err == ERROR_NO_SYSTEM_RESOURCES ) {
			TRACE( "error:  ERROR_NO_SYSTEM_RESOURCES\n" );
		}
		else if ( err == ERROR_INVALID_DATA ) {
			TRACE( "error:  ERROR_INVALID_DATA\n" );
		}
		wglMakeCurrent( hOldDC, hOldRC );
		return false;
	}
	
	// Get the device context.
	m_hPBufferDC = wglGetPbufferDCARB( m_hPBuffer );
	if ( !m_hPBufferDC ) {
		TRACE( "pbuffer creation error:  wglGetPbufferDCARB() failed\n" );
		wglMakeCurrent( hOldDC, hOldRC );
		return false;
	}
	
	// Create a gl context for the p-buffer.
	m_hPBufferRC = wglCreateContext( m_hPBufferDC );
	if ( !m_hPBufferRC ) {
		TRACE( "pbuffer creation error:  wglCreateContext() failed\n" );
		wglMakeCurrent( hOldDC, hOldRC );
		return false;
	}
	
	if( !wglShareLists( hOldRC, m_hPBufferRC ) ) {
		TRACE( "pbuffer: wglShareLists() failed\n" );
		wglMakeCurrent( hOldDC, hOldRC );
		return false;
	}
	
	int32	w, h;

	// Determine the actual width and height we were able to create.
	wglQueryPbufferARB( m_hPBuffer, WGL_PBUFFER_WIDTH_ARB, &w );
	wglQueryPbufferARB( m_hPBuffer, WGL_PBUFFER_HEIGHT_ARB, &h );

//	TRACE( "w: %d, h: %d\n", w, h );
	
	if( w && h ) {

		m_ui32Mode = ui32Mode;
		m_ui32Width = w;
		m_ui32Height = h;

		if( ui32Mode != PBUFFER_TEXTURE_NONE ) {
			// set current device context
			wglMakeCurrent( m_hPBufferDC, m_hPBufferRC );

			if( m_ui32PBufferTexID )
				glDeleteTextures( 1, &m_ui32PBufferTexID );
		
			glGenTextures( 1, &m_ui32PBufferTexID );
			glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexID );

			if( ui32Mode == PBUFFER_TEXTURE_RGBA ) {
				// create texture
				glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, m_ui32Width, m_ui32Height, 0, GL_RGBA, GL_FLOAT, 0 );

				glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
			}
			else if( ui32Mode == PBUFFER_TEXTURE_DEPTH ) {
		
				GLenum	eDepthFormat;
				int32	i32DepthBits;
				glGetIntegerv( GL_DEPTH_BITS, &i32DepthBits );
				
				if( i32DepthBits == 16)
					eDepthFormat = GL_DEPTH_COMPONENT16_SGIX;
				else
					eDepthFormat = GL_DEPTH_COMPONENT24_SGIX;

				glTexImage2D( GL_TEXTURE_2D, 0, eDepthFormat, m_ui32Width, m_ui32Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0 );

				glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
				glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
			}

			glBindTexture( GL_TEXTURE_2D, 0 );
		}

	}

	wglMakeCurrent( hOldDC, hOldRC );

	return true;
}

void
PBufferC::copy_texture()
{
	if( !m_hPBuffer )
		return;

	// make pbuffer into texture
	if( m_ui32PBufferTexID ) {
		// copy texture
		glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexID );
		glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_ui32Width, m_ui32Height );
		glBindTexture( GL_TEXTURE_2D, 0 );

		CHECK_GL_ERROR( "copy_texture" );
	}
}

void
PBufferC::bind()
{
	if( !m_hPBuffer )
		return;

	glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexID );
	CHECK_GL_ERROR( "bind" );
}

void
PBufferC::destroy()
{
	// unset old context if it's currently in use
	HDC		hOldDC = wglGetCurrentDC();
	HGLRC	hOldRC = wglGetCurrentContext();
	if( hOldDC == m_hPBufferDC && hOldRC == m_hPBufferRC )
		wglMakeCurrent( NULL, NULL );

	if( m_hPBufferRC )
		wglDeleteContext( m_hPBufferRC );
	m_hPBufferRC = 0;

	if( m_hPBuffer ) {
		wglReleasePbufferDCARB( m_hPBuffer, m_hPBufferDC );
		wglDestroyPbufferARB( m_hPBuffer );
	}
	m_hPBuffer = 0;
	m_hPBufferDC = 0;

	if( m_ui32PBufferTexID )
		glDeleteTextures( 1, &m_ui32PBufferTexID );
	m_ui32PBufferTexID = 0;
}

uint32
PBufferC::get_width() const
{
	return m_ui32Width;
}

uint32
PBufferC::get_height() const
{
	return m_ui32Height;
}

bool
PBufferC::begin_draw()
{
	if( !m_hPBuffer )
		return false;

	// Handle device lost
    int32	i32Lost = 0;
    wglQueryPbufferARB( m_hPBuffer, WGL_PBUFFER_LOST_ARB, &i32Lost );
    if( i32Lost ) {
        destroy();
        init( m_ui32Width, m_ui32Height, m_ui32Mode );
	}

	m_hPrevDC = wglGetCurrentDC();
	m_hPrevRC = wglGetCurrentContext();

	// set current device context
	wglMakeCurrent( m_hPBufferDC, m_hPBufferRC );

	// get old viewport
//	glGetIntegerv( GL_VIEWPORT, m_i32OldViewport );

	// Set viewport correctly
	glViewport( 0, 0, m_ui32Width, m_ui32Height );

	CHECK_GL_ERROR( "begin_draw" );

	return true;
}


void
PBufferC::end_draw()
{
	if( !m_hPBuffer )
		return;

	wglMakeCurrent( m_hPrevDC, m_hPrevRC );

	CHECK_GL_ERROR( "end_draw" );

//	glViewport( m_i32OldViewport[0], m_i32OldViewport[1], m_i32OldViewport[2], m_i32OldViewport[3] );
}

bool
PBufferC::is_initialised() const
{
	if( m_hPBuffer )
		return true;
	return false;
}
