/*
 =======================================================================================
  Name        : OGL_Thread.c
  Author      : Bart     
  Description :
 =======================================================================================
 */

/*
 =======================================================================================
    Include 
 =======================================================================================
 */

#include <windows.h>
#include <gl/gl.h>

#include "OGL_Options.h"
#include "OGL_Errors.h"
#include "OGL_Thread.h"


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#ifdef __cplusplus
extern "C" 
{
#endif
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/


// opengl thread
HANDLE g_h_OpenGLThread;
DWORD g_xThreadId;
#define M_h_GetOpenGLThread()              (g_h_OpenGLThread)
#define M_v_SetOpenGLThread(_hThread)      {g_h_OpenGLThread = _hThread;}
#define M_x_GetOpenGLThreadId()            (g_xThreadId)

//events
#define C_sz_ThreadCreationEvtName	"CREATETHREADEVT"
HANDLE hEvent_ThreadCreation = NULL; //, hEvent_ThreadCreation2;

#define C_sz_WaitingForBooleanEvtName	"WAIT4BOOLEVT"
HANDLE hEvent_WaitingForBoolean = NULL; //, hEvent_WaitingForBoolean2;

#define C_sz_JustWaiting	"JUSTWAIT"
HANDLE hEvent_WaitForSynchro = NULL; //, hEvent_Wait2;

/*
//mutexes
#define C_sz_CounterMutexName "FIFOCOUNT"
HANDLE hMutex_FIFOCount = NULL;
*/


/*
// FIFO
#define FIFO_MEM_SIZE 10
long* g_plFIFOStartPointer = NULL;
long g_lFIFOCurrentOffset;
long g_lFIFOCount;

#define M_GetParam(offset)    g_plFIFOStartPointer[(offset)%FIFO_MEM_SIZE]
*/


/***** Called Functions *****/
#include "OGL_Func.c"

/*
long fn_lMallocNLong(int _nSize)
{
	long lRet;

	WaitForSingleObject( hMutex_FIFOCount, INFINITE ); //blocking on the mutex


	if (g_lFIFOCount+_nSize > FIFO_MEM_SIZE) { // >= oder > ?

		M_OUTPUT("Overflow\n");
		
	  ReleaseMutex( hMutex_FIFOCount );
		OGLThread_fn_vWaitForSynchro();
	  WaitForSingleObject(hMutex_FIFOCount, INFINITE);
		assert(g_lFIFOCount == 0);
	}

	assert (g_lFIFOCount >= 0);
	g_lFIFOCount += _nSize;

	lRet = g_lFIFOCurrentOffset;
	g_lFIFOCurrentOffset = (g_lFIFOCurrentOffset + _nSize) % FIFO_MEM_SIZE;

	ReleaseMutex( hMutex_FIFOCount );
	return lRet;
}

void fn_vFreeNLong(int _nSize)
{
	WaitForSingleObject( hMutex_FIFOCount, INFINITE );
	
	g_lFIFOCount -= _nSize;
	assert (g_lFIFOCount >= 0);
	ReleaseMutex( hMutex_FIFOCount );
}
*/


/***** Treat Message *****/

BOOL fn_b_OpenGLThreadTreatMessage(UINT _message, WPARAM _wParam, LPARAM _lParam)
{
	GLboolean bResidence; // for glAreTexturesResident
	long* pParam = (long*)_wParam;

	switch (_message)
	{
		case OGLT0_WAIT :
			SetEvent(hEvent_WaitForSynchro);
			break;
		case OGLT0_CREATE :
			fn_OGLThread_vCreate();
			break;
		case OGLT0_RESUME :
			fn_OGLThread_vResume();
			break;
		case OGLT0_SWAP :
			fn_OGLThread_vSwap();
			break;
		case OGLT0_INITFLAGS :
			fn_OGLThread_vInitOglFlags();
			break;
		case OGLT0_FLUSH :
			M_OGL_ASSERT( glFinish (); )
			M_OGL_ASSERT( glFlush(); )
			break;
		case OGLT0_ALPHATEST :
			M_OGL_ASSERT( glAlphaFunc( GL_GREATER, 0.5f ); )
			break;
		case OGLT0_LOCKDEVICE :
			fn_OGLThread_vLockDevice();
			break;
		case OGLT0_UNLOCKDEVICE :
			fn_OGLThread_vUnlockDevice();
			break;
		case OGLT0_BLACKPOLY :
			fn_OGLThread_vSendBlackPolygon();
			break;
		case OGLT0_FOGPARAM :
			fn_OGLThread_vSetFogParams();
			break;
		
		/* 1Long */
		case OGLT1_ENABLE :
			M_OGL_ASSERT( glEnable( (GLenum)_wParam ); )
			break;
		case OGLT1_DISABLE :
			M_OGL_ASSERT( glDisable( (GLenum)_wParam ); )
			break;
		case OGLT1_DEPTHMASK :
			M_OGL_ASSERT( glDepthMask( (GLboolean)_wParam ); )
			break;
		case OGLT1_DELETETEXTURE :
			M_OGL_ASSERT( glDeleteTextures(1, (GLuint*) &_wParam ); )
			break;
		case OGLT1_BINDTEXTURE :
			M_OGL_ASSERT( glBindTexture(GL_TEXTURE_2D, (GLuint) _wParam ); )
			break;
		case OGLT1_TEXTUREENV :
			M_OGL_ASSERT( glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, (GLuint) _wParam ); )
			break;
		case OGLT1_RENDER :
			fn_OGLThread_vRender((GLI_tdScreenVertx*) _wParam );
			break;
		case OGLT1_LOADTEXTURES :
			fn_OGLThread_vLoadTextures((GLI_tdstTexture*) _wParam );
			break;


		/* 2Long */
		case OGLT2_PIXELTRANSFER :
			M_OGL_ASSERT( glPixelTransferi( (GLenum)_wParam, (GLint)_lParam ); )
			break;
		case OGLT2_BLENDFUNC :
			M_OGL_ASSERT( glBlendFunc( (GLenum)_wParam, (GLenum)_lParam ); )
			break;
		case OGLT2_WRITEBITMAP :
			fn_OGLThread_vWrite16bBitmapToBackBuffer(_wParam, _lParam );
			break;
		case OGLT2_CLEARDEVICE :
			fn_OGLThread_vClearDevice((_wParam & 2), (_wParam & 1), (unsigned long) _lParam);
			break;
	
		/* 3Long */
/*		case OGLT3_CLEARDEVICE :
			fn_OGLThread_vClearDevice((BOOL) M_GetParam(_wParam+0), (BOOL) M_GetParam(_wParam+1), (unsigned long) M_GetParam(_wParam+2));
			fn_vFreeNLong(3);
			break;
*/

		/* return boolean value */
		case OGLT1_ISTEXTURE :
			M_OGL_ASSERT( pParam[0] = (long)glIsTexture( (GLuint)pParam[1] ); )
			SetEvent(hEvent_WaitingForBoolean);
			break;
		case OGLT1_ISENABLED :
			M_OGL_ASSERT( pParam[0] = (long)glIsEnabled( (GLenum)pParam[1] ); )
			SetEvent(hEvent_WaitingForBoolean);
			break;
		case OGLT1_ISRESIDENT :
			M_OGL_ASSERT( pParam[0] = (long)glAreTexturesResident( 1, (GLuint*) &pParam[1], &bResidence ); )
			SetEvent(hEvent_WaitingForBoolean);
			break;

		default : // OGLT0_EXIT or else ?
			return FALSE;
	}

	return TRUE;
}


/***** Thread Loop *****/

DWORD WINAPI fn_x_OpenGLThreadEntryPoint(LPVOID _p_vParameter)
{
    MSG stMsg;
	BOOL bNoExit;

	_p_vParameter = _p_vParameter;

	PeekMessage(&stMsg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // to force the system to create the message queue
	SetEvent(hEvent_ThreadCreation);

    // message loop
	while (bNoExit)
	{
		GetMessage(&stMsg, NULL, WM_USER, WM_USER + OGLT0_NB_MSG_MAX);

		assert(stMsg.message >= WM_USER);
		assert(stMsg.message <= WM_USER + OGLT0_NB_MSG_MAX);

		bNoExit = fn_b_OpenGLThreadTreatMessage( stMsg.message - WM_USER, stMsg.wParam, stMsg.lParam );
	}

    return 0;
}




/***** Calling Functions *****/

#define M_v_RequestToOpenGLThread(_uiMessage, _WParam, _LParam)\
{\
	/*PostThreadMessage(M_x_GetOpenGLThreadId(), WM_USER+_uiMessage, (WPARAM)0, (LPARAM)_Param);*/\
	PostThreadMessage(M_x_GetOpenGLThreadId(), WM_USER+_uiMessage, (WPARAM)_WParam, (LPARAM)_LParam);\
}


void OGLThread_fn_vPostCmd(int _nCommandId)
{
	M_v_RequestToOpenGLThread(_nCommandId, 0, 0);
}

void OGLThread_fn_vPostCmd_1Long(int _nCommandId, long _lParam1)
{
	M_v_RequestToOpenGLThread(_nCommandId, _lParam1, 0);
}

void OGLThread_fn_vPostCmd_2Long(int _nCommandId, long _lParam1, long _lParam2)
{
	M_v_RequestToOpenGLThread(_nCommandId, _lParam1, _lParam2);
}

/*
void OGLThread_fn_vPostCmd_3Long(int _nCommandId, long _lParam1, long _lParam2, long _lParam3)
{
	long lParam;
	lParam = fn_lMallocNLong(3);
	M_GetParam(lParam+0) = _lParam1;
	M_GetParam(lParam+1) = _lParam2;
	M_GetParam(lParam+2) = _lParam3;
	M_v_RequestToOpenGLThread(_nCommandId, lParam, 0);
}
*/

/* send command to thread then wait for the returning boolean value */
BOOL OGLThread_fn_bPostCmdAndWait(int _nCommandId, long _lParam1) 
{
	long pParam[2];
	
	pParam[1] = _lParam1;

	M_v_RequestToOpenGLThread(_nCommandId, pParam, 0);
	WaitForSingleObject(hEvent_WaitingForBoolean, INFINITE); // reset event when return
	
	return pParam[0];
}




/***** Create / Resume context *****/

void OGLThread_fn_vCreateContext(BOOL _bReinitDevice)
{
    if (_bReinitDevice)
	{
		/* Create the rendering context and make it current */
		OGLThread_fn_vWaitForSynchro();
		OGLThread_fn_vPostCmd(OGLT0_CREATE);
	}

    /* render initialization */
	OGLThread_fn_vWaitForSynchro();
	OGLThread_fn_vPostCmd(OGLT0_INITFLAGS); //GLI_fn_vInitOglFlags();

	OGLThread_fn_vWaitForSynchro();
}

void OGLThread_fn_vResumeContext(BOOL _bReinitDevice)
{
	if (!_bReinitDevice)
		return;

	OGLThread_fn_vWaitForSynchro();
	OGLThread_fn_vPostCmd(OGLT0_RESUME);
	OGLThread_fn_vWaitForSynchro();
}



/***** create / terminate thread, events & mutexes *****/

void OGLThread_fn_vCreateThread(void)
{
	hEvent_ThreadCreation = CreateEvent(NULL, TRUE, FALSE, NULL /*C_sz_ThreadCreationEvtName*/); // create event (nonsignaled state)

    M_v_SetOpenGLThread( CreateThread( NULL, 0, fn_x_OpenGLThreadEntryPoint, NULL, 0, &M_x_GetOpenGLThreadId() ) );
	SetPriorityClass(M_h_GetOpenGLThread(), HIGH_PRIORITY_CLASS);

	WaitForSingleObject(hEvent_ThreadCreation, INFINITE); // wait for the event to be signaled

	if (hEvent_ThreadCreation)
		CloseHandle(hEvent_ThreadCreation);

	hEvent_WaitingForBoolean = CreateEvent(NULL, FALSE, FALSE, C_sz_WaitingForBooleanEvtName); // create event (no manual-reset)
	hEvent_WaitForSynchro = CreateEvent(NULL, FALSE, FALSE, C_sz_JustWaiting); // create event (no manual-reset)

	/*
	hMutex_FIFOCount = CreateMutex(NULL, FALSE, C_sz_CounterMutexName);

	g_plFIFOStartPointer = (long*)GLI_MDRV_p_vTmpMalloc(FIFO_MEM_SIZE*sizeof(long));
	g_lFIFOCurrentOffset = 0;
	g_lFIFOCount = 0;
	*/
}

void OGLThread_fn_vTerminateThread()
{
	OGLThread_fn_vWaitForSynchro();

	if (hEvent_WaitingForBoolean)
		CloseHandle(hEvent_WaitingForBoolean); // ?
	hEvent_WaitingForBoolean = NULL;

	if (hEvent_WaitForSynchro)
		CloseHandle(hEvent_WaitForSynchro); // ?
	hEvent_WaitForSynchro = NULL;

	/*
	if (hMutex_FIFOCount)
		CloseHandle(hMutex_FIFOCount); // ?
	hMutex_FIFOCount = NULL;

	if (g_plFIFOStartPointer)
		GLI_MDRV_vTmpFree(g_plFIFOStartPointer);
	g_plFIFOStartPointer = NULL;
	*/

	OGLThread_fn_vPostCmd(OGLT0_EXIT);
}



/***** synkronizazion *****/

void OGLThread_fn_vWaitForSynchro()
{
	OGLThread_fn_vPostCmd(OGLT0_WAIT);
	WaitForSingleObject(hEvent_WaitForSynchro, INFINITE); // wait for the event to be signaled
}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#ifdef __cplusplus
} //extern "C"
#endif
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
