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


using namespace PajaTypes;
using namespace MASPlugin;

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;
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;

static PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB = NULL;

static bool g_bPBufferBindTexRGBInit = false;
static bool g_bPBufferBindTexDepthInit = false;
static PFNWGLBINDTEXIMAGEARBPROC			wglBindTexImageARB = NULL;
static PFNWGLRELEASETEXIMAGEARBPROC			wglReleaseTexImageARB = NULL;
static PFNWGLSETPBUFFERATTRIBARBPROC		wglSetPbufferAttribARB = NULL;



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

	HDC	hDC = wglGetCurrentDC();

	// initialize extension entry points
//	wglMakeContextCurrentARB = (PFNWGLMAKECONTEXTCURRENTARBPROC)wglGetProcAddress( "wglMakeContextCurrentARB" );
	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" );

	// for faster rendering...
	wglBindTexImageARB = (PFNWGLBINDTEXIMAGEARBPROC)wglGetProcAddress( "wglBindTexImageARB" );
	wglReleaseTexImageARB = (PFNWGLRELEASETEXIMAGEARBPROC)wglGetProcAddress( "wglReleaseTexImageARB" );
	wglSetPbufferAttribARB = (PFNWGLSETPBUFFERATTRIBARBPROC)wglGetProcAddress( "wglSetPbufferAttribARB" );


	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;

	if( wglBindTexImageARB && wglReleaseTexImageARB && wglSetPbufferAttribARB ) {
		TRACE( "Bind tex image OK\n" );
//		g_bPBufferBindTexRGBInit = true;
	}


	g_bPBufferExtInit = true;

	return true;
}



PBufferC::PBufferC() :
	m_hPBuffer( 0 ),
	m_hPBufferDC( 0 ),
	m_hPBufferRC( 0 ),
	m_ui32PBufferTexRGBAID( 0 ),
	m_ui32PBufferTexDepthID( 0 ),
	m_ui32Width( 0 ),
	m_ui32Height( 0 ),
	m_bBindToRenderTextureRGBA( false ),
	m_bBindToRenderTextureDepth( false )
{
	// empty
}

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

#define MAX_PFORMATS 256
#define MAX_ATTRIBS  32

bool
PBufferC::init( uint32 ui32Width, uint32 ui32Height, uint32 ui32Mode )
{
	HDC		hOldDC = wglGetCurrentDC();
	HGLRC	hOldRC = wglGetCurrentContext();

	// if extensions cannot be initialsed, bail out.
	if( !init_extensions() )
	{
		TRACE( "init extensions failed.\n" );
		return false;
	}

	m_ui32Mode = ui32Mode;

	// 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.
  memset( iAttributes, 0, sizeof(int)*2*MAX_ATTRIBS);
  memset( fAttributes, 0, sizeof(float)*2*MAX_ATTRIBS);
  // Since we are trying to create a pbuffer, the pixel format we
  // request (and subsequently use) must be "p-buffer capable".
  iAttributes[nIAttribs  ] = WGL_DRAW_TO_PBUFFER_ARB;
  iAttributes[++nIAttribs] = GL_TRUE;
  // we are asking for a pbuffer that is meant to be bound
  // as an RGBA texture - therefore we need a color plane

	if( g_bPBufferBindTexRGBInit && (ui32Mode & PBUFFER_TEXTURE_RGBA) )
	{
		iAttributes[++nIAttribs] = WGL_BIND_TO_TEXTURE_RGBA_ARB;
		iAttributes[++nIAttribs] = GL_TRUE;
	}
	if( g_bPBufferBindTexDepthInit && (ui32Mode & PBUFFER_TEXTURE_DEPTH) )
	{
		iAttributes[++nIAttribs] = WGL_BIND_TO_TEXTURE_DEPTH_NV;
		iAttributes[++nIAttribs] = GL_TRUE;
	}

	iAttributes[++nIAttribs] = WGL_DEPTH_BITS_ARB;
	iAttributes[++nIAttribs] = 1;
	
	iAttributes[++nIAttribs] = WGL_STENCIL_BITS_ARB;
	iAttributes[++nIAttribs] = 1;
	
	iAttributes[++nIAttribs] = WGL_SUPPORT_OPENGL_ARB;
	iAttributes[++nIAttribs] = GL_TRUE;

	int format;
	int pformat[MAX_PFORMATS];
	unsigned int nformats;
	if( !wglChoosePixelFormatARB( hOldDC, iAttributes, fAttributes, MAX_PFORMATS, pformat, &nformats ) ) {
		TRACE( "pbuffer creation error:  Couldn't find a suitable pixel format.\n" );
		return false;
	}
	format = pformat[0];

	TRACE( "%d formats\n", nformats );

	// Create the p-buffer.

  // Set up the pbuffer attributes
  memset(iAttributes,0,sizeof(int)*2*MAX_ATTRIBS);
  nIAttribs = 0;

	if( g_bPBufferBindTexRGBInit && (ui32Mode & PBUFFER_TEXTURE_RGBA) )
	{
		// the render texture format is RGBA
		iAttributes[nIAttribs] = WGL_TEXTURE_FORMAT_ARB;
		iAttributes[++nIAttribs] = WGL_TEXTURE_RGBA_ARB;
	}

	if( g_bPBufferBindTexDepthInit && (ui32Mode & PBUFFER_TEXTURE_DEPTH) )
	{
		// the render texture format is RGBA
		iAttributes[nIAttribs] = WGL_TEXTURE_FORMAT_ARB;
		iAttributes[++nIAttribs] = WGL_TEXTURE_DEPTH_COMPONENT_NV;
	}

	if( g_bPBufferBindTexRGBInit || g_bPBufferBindTexDepthInit )
	{
		// the render texture target is GL_TEXTURE_2D
		iAttributes[++nIAttribs] = WGL_TEXTURE_TARGET_ARB;
		iAttributes[++nIAttribs] = WGL_TEXTURE_2D_ARB;
	}

	m_hPBuffer = wglCreatePbufferARB( hOldDC, format, ui32Width, ui32Height, iAttributes );
	if ( !m_hPBuffer ) {
		DWORD err = GetLastError();
		TRACE( "pbuffer creation error:  wglCreatePbufferARB() failed (%d)\n", err );

		CHECK_GL_ERROR( "pbuffer creation.\n" );

		return false;
	}
	
	// Get the device context.
	m_hPBufferDC = wglGetPbufferDCARB( m_hPBuffer );
	if ( !m_hPBufferDC )
	{
		TRACE( "pbuffer creation error:  wglGetPbufferDCARB() failed\n" );
		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" );
		return false;
	}
	
	if( !wglShareLists( hOldRC, m_hPBufferRC ) ) {
		TRACE( "pbuffer: wglShareLists() failed\n" );
		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( "Created a %d x %d pbuffer\n", w, h );

	if( w && h ) {

		m_ui32Width = w;
		m_ui32Height = h;

		// reset old dependancies
		glBindTexture( GL_TEXTURE_2D, 0 );
		glBindTexture( GL_TEXTURE_1D, 0 );

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

		if( m_ui32PBufferTexRGBAID )
			glDeleteTextures( 1, &m_ui32PBufferTexRGBAID );
		if( m_ui32PBufferTexDepthID )
			glDeleteTextures( 1, &m_ui32PBufferTexDepthID );

		if( m_ui32Mode & PBUFFER_TEXTURE_RGBA )
		{
			glGenTextures( 1, &m_ui32PBufferTexRGBAID );

			glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexRGBAID );

			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 );

			// If not using render to texture extension, create the texture.
			if( !g_bPBufferBindTexRGBInit )
			{
				// create texture
				glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_FLOAT, 0 );
			}
		}
		
		if( m_ui32Mode & PBUFFER_TEXTURE_DEPTH )
		{
			glGenTextures( 1, &m_ui32PBufferTexDepthID );

			glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexDepthID );

			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 );

			GLenum	eDepthFormat;
			int32	i32DepthBits;
			glGetIntegerv( GL_DEPTH_BITS, &i32DepthBits );
			
			if( i32DepthBits == 16)
				eDepthFormat = GL_DEPTH_COMPONENT16_ARB;
			else
				eDepthFormat = GL_DEPTH_COMPONENT24_ARB;

			// If not using render to texture extension, create the texture.
			if( !g_bPBufferBindTexDepthInit )
			{
				// create texture
				glTexImage2D( GL_TEXTURE_2D, 0, eDepthFormat, m_ui32Width, m_ui32Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0 );
			}
		}

	}

	wglMakeCurrent( hOldDC, hOldRC );

	return true;
}

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

	glFlush();

	// make pbuffer into texture

	if( m_ui32Mode & PBUFFER_TEXTURE_RGBA )
	{
		if( g_bPBufferBindTexRGBInit ) {
			// Do nothing.
		}
		else {
			if( m_ui32PBufferTexRGBAID ) {
				// copy texture
				glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexRGBAID );
				glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_ui32Width, m_ui32Height );
				glBindTexture( GL_TEXTURE_2D, 0 );
			}
		}
	}

	if( m_ui32Mode & PBUFFER_TEXTURE_DEPTH )
	{
		if( g_bPBufferBindTexDepthInit ) {
			// Do nothing.
		}
		else {
			if( m_ui32PBufferTexDepthID ) {
				// copy texture
				glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexDepthID );
				glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, m_ui32Width, m_ui32Height );
				glBindTexture( GL_TEXTURE_2D, 0 );
			}
		}
	}
}

void
PBufferC::bind( uint32 ui32Mode )
{
	if( (ui32Mode == PBUFFER_TEXTURE_RGBA) && m_ui32PBufferTexRGBAID )
	{
		CHECK_GL_ERROR( "PBufferC::bind_texture [has tex ID]" );

		glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexRGBAID );

		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 );

		if( g_bPBufferBindTexRGBInit )
		{
			if( !m_bBindToRenderTextureRGBA )
			{
				// bind the pbuffer to the render texture object
				if( wglBindTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB ) == FALSE )
				{
					CHECK_GL_ERROR( "OpenGLBufferC::flush() bind render to texture" );
				}
				m_bBindToRenderTextureRGBA = true;
			}
		}
	}

	if( (ui32Mode == PBUFFER_TEXTURE_DEPTH) &&  m_ui32PBufferTexDepthID )
	{
		CHECK_GL_ERROR( "PBufferC::bind_texture [has tex ID]" );

		glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexDepthID );

		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 );

		if( g_bPBufferBindTexDepthInit )
		{
			if( !m_bBindToRenderTextureDepth )
			{
				// bind the pbuffer to the render texture object
				if( wglBindTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB ) == FALSE )
				{
					CHECK_GL_ERROR( "OpenGLBufferC::flush() bind render to texture" );
				}
				m_bBindToRenderTextureDepth = true;
			}
		}

	}



/*	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_ui32PBufferTexRGBAID )
		glDeleteTextures( 1, &m_ui32PBufferTexRGBAID );
	m_ui32PBufferTexRGBAID = 0;

	if( m_ui32PBufferTexDepthID )
		glDeleteTextures( 1, &m_ui32PBufferTexDepthID );
	m_ui32PBufferTexDepthID = 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;

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

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

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

	if( !m_hPBuffer )
		return false;

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

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

	// reset old dependancies
	glBindTexture( GL_TEXTURE_2D, 0 );
	glBindTexture( GL_TEXTURE_1D, 0 );

	// release the pbuffer from the render texture object
	if( g_bPBufferBindTexRGBInit && m_ui32PBufferTexRGBAID )
	{
		glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexRGBAID );
		if( wglReleaseTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB ) == FALSE )
		{
			CHECK_GL_ERROR( "wglReleaseTexImageARB" );
		}
		m_bBindToRenderTextureRGBA = false;
	}
	if( g_bPBufferBindTexDepthInit && m_ui32PBufferTexDepthID )
	{
		glBindTexture( GL_TEXTURE_2D, m_ui32PBufferTexDepthID );
		if( wglReleaseTexImageARB( m_hPBuffer, WGL_FRONT_LEFT_ARB ) == FALSE )
		{
			CHECK_GL_ERROR( "wglReleaseTexImageARB" );
		}
		m_bBindToRenderTextureDepth = false;
	}

	glBindTexture( GL_TEXTURE_2D, 0 );

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


	// 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" );
}

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