#include "SND\snddef.h"

#include "SNDinc.h"
#include "snd.h"

#include <windows.h>
#include <process.h>


#include "sndthrd.h"

typedef struct {
	SND_td_pfn_vSoundCallback	pfnFunction;
	long						lParam;
} stThreadCallbackParam;

static HANDLE g_hMutexSnd=NULL;
static BOOL g_bFlagSnd=FALSE;				//mis à TRUE quand un WaitForSingleObjet à réussi (sera à TRUE pour 2 WaitForSingle réantrant)
static BOOL g_bAnOtherThreadWaiting=FALSE; //mis à TRUE quand une Thread est sur WaitForSingleObject
static char szLastFileCalling[128]={""};
static int szLastLineCalling=0;
static DWORD dwLastThreadCalling=0;

static BOOL g_bInitDone=FALSE;
static CRITICAL_SECTION CriticalSectionForError;

static HANDLE hEventForError=NULL;
static char szLastFileCallingEvent[128]={""};
static int szLastLineCallingEvent=0;
static DWORD dwLastThreadCallingEvent=0;

void SND_fn_vInitCriticalSectionForErrorDisplay();
void SND_fn_vDesInitCriticalSectionForErrorDisplay();

//---------------------------------------
//	Init la gestion de section critique
//---------------------------------------
void SND_CALL SND_fn_vInitThreadSnd(void)
{
	g_hMutexSnd=CreateMutex(NULL,TRUE,NULL);
	g_bFlagSnd=FALSE;
    g_bInitDone=TRUE;
	g_bAnOtherThreadWaiting=FALSE;

	SND_fn_vInitCriticalSectionForErrorDisplay();
} 

//---------------------------------------
//	DesInit la gestion de section critique
//---------------------------------------
void SND_CALL SND_fn_vDesInitThreadSnd(void)
{
	HANDLE tmp_g_hMutexSnd;

	SND_fn_vDesInitCriticalSectionForErrorDisplay();

	tmp_g_hMutexSnd=g_hMutexSnd;
	g_hMutexSnd=NULL;
	ReleaseMutex(tmp_g_hMutexSnd);
	CloseHandle(tmp_g_hMutexSnd);
}

//-----------------------------------------
//	Note le dernier appel à EnterCriticalSection 
//------------------------------------------
void SND_CALL SND_fn_vMouchardThreadsnd(char* file,int line)
{
	strncpy(szLastFileCalling,file,sizeof(szLastFileCalling));
	szLastLineCalling=line;
    dwLastThreadCalling=GetCurrentThreadId();
}

//-----------------------------------------
//	Note le dernier lockeur de l'Event
//------------------------------------------
void SND_CALL SND_fn_vMouchardThreadsndEvent(char* file,int line)
{
	strncpy(szLastFileCallingEvent,file,sizeof(szLastFileCallingEvent));
	szLastLineCallingEvent=line;
    dwLastThreadCallingEvent=GetCurrentThreadId();
}

//-----------------------------------------
//	Entrée en section critique (bloquant à l'infini)
//------------------------------------------
void SND_CALL SND_fn_vEnterCriticalSectionThreadSnd_(void)
{
	DWORD dwRet;
	DWORD dwErr;
	DWORD delay;
    char texte[256];

    if (!g_bInitDone)
        SND_fn_vDisplayError(E_uwSndInitNotDoneError,"");
      
    if (g_hMutexSnd!=NULL)
	{
		delay=INFINITE;
		
		if ((dwRet=WaitForSingleObject(g_hMutexSnd,delay))==WAIT_OBJECT_0)
		{
			g_bAnOtherThreadWaiting=FALSE;
			g_bFlagSnd=TRUE;
			return;
		}

		g_bAnOtherThreadWaiting=FALSE;

		dwErr=GetLastError();
        sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling);
	    SND_fn_vDisplayError(E_uwSndThreadDeadLockError,texte);
        return;
	}
	
    SND_fn_vDisplayError(E_uwSndThreadMultiThreadDetectedError,texte);
}

/*
void SND_CALL SND_fn_vEnterCriticalSectionWithMsgPumpThreadSnd()
{
	DWORD dwRet;
	DWORD delay;
#ifdef DEBUG_SON
	static BOOL bReentraceDetected=FALSE;
#endif
    char texte[256];
	MSG msg;

    if (!g_bInitDone)
        SND_fn_vDisplayError(E_uwSndInitNotDoneError,"");
      
    if (g_hMutexSnd!=NULL)
	{
#ifdef DEBUG_SON
		dwRet=WaitForSingleObject(g_hMutexSnd,0);

		if (dwRet==WAIT_OBJECT_0)
		{//l'objet a pu être locké
			if (g_bFlagSnd)
			{//reentrance detected
				if (!bReentraceDetected)
				{//first reentrance (single case)
					bReentraceDetected=TRUE;
                    sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling,szLastLineCalling);
                    DebugBreak();
					SND_fn_vDisplayError(E_uwSndThreadReentranceDetectedError,
                        texte);
				}
				else
					//critical reentrance detected !!!!!!!!!!!!!!!!!!!!!!!!!!
					//if you don't stop this, a stack overflow may occur
					DebugBreak();
			}
			g_bFlagSnd=TRUE;

			return;
		}
		//l'objet n'est pas disponible
#endif //DEBUG_SON

		delay=10;
		
		//for debugging
        sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling);

		while (TRUE)
		{
			dwRet=WaitForSingleObject(g_hMutexSnd,delay);

			if (dwRet==WAIT_OBJECT_0)
			{
				g_bAnOtherThreadWaiting=FALSE;
				g_bFlagSnd=TRUE;
				return;
			}

			while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
				{
					TranslateMessage(&msg);
					DispatchMessage(&msg);
				}

			//to allow other applications  to run
			Sleep(10);
		}

//		g_bAnOtherThreadWaiting=FALSE;

	}
	
    //SND_fn_vDisplayError(E_uwSndThreadMultiThreadDetectedError,texte);
}
*/

//	Attente du mutex pendant un temps limité
//		si mutex dispo, lockage et return TRUE
//		si mutex non dispo au bout du temps, return FALSE
//	Pas de protection contre la réentrance (cas particulier de DisplayError)
//	Appelée uniquement pour Release Driver

SndBool SND_CALL SND_fn_bTryToEnterCriticalSectionThreadSnd()
{
	DWORD dwRet;
	DWORD dwErr;
	DWORD delay;
//	static BOOL bReentraceDetected=FALSE;
    char texte[256];

    if (!g_bInitDone)
        SND_fn_vDisplayError(E_uwSndInitNotDoneError,"");
      
    if (g_hMutexSnd!=NULL)
	{
#ifdef DEBUG_SON
		dwRet=WaitForSingleObject(g_hMutexSnd,0);

		if (dwRet==WAIT_OBJECT_0)
		{//l'objet a pu être locké
			g_bFlagSnd=TRUE;

			return TRUE;
		}
		//l'objet n'est pas disponible
#endif //DEBUG_SON

		delay=100;
		
        sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling);

		dwRet=WaitForSingleObject(g_hMutexSnd,delay);

		if (dwRet==WAIT_OBJECT_0)
		{
			g_bAnOtherThreadWaiting=FALSE;
			g_bFlagSnd=TRUE;
			return TRUE;
		}

		g_bAnOtherThreadWaiting=FALSE;

#ifdef DEBUG_SON
/*
		DebugBreak();
		if (Erm_fn_iMessageBox(HWND_DESKTOP,"Do you want to retry fo infinite Time ?","Possible DeadLock",MB_YESNO)==IDYES)
			if ((dwRet=WaitForSingleObject(g_hMutexSnd,INFINITE))==WAIT_OBJECT_0)
			{
				g_bFlagSnd=TRUE;
				return TRUE;
			}
			*/
#endif

		dwErr=GetLastError();
	    SND_fn_vDisplayError(E_uwSndSystemError,"DeadLock occured when waiting for Sound Mutex !\nProgram will continue although");
        return FALSE;
	}
	
    SND_fn_vDisplayError(E_uwSndThreadMultiThreadDetectedError,texte);

	return FALSE;
}

//---------------------------------------
//	Sortie d'une section critique
//--------------------------------------
void SND_CALL SND_fn_vQuitCriticalSectionThreadSnd(void)
{
	g_bFlagSnd=FALSE;
	ReleaseMutex(g_hMutexSnd);
}

//----------------------------------------
//	Attente bloquante (mais discrète) qu'un pointeur prenne la bonne valeur
//----------------------------------------------
void SND_CALL SND_fn_vWaitForValueThreadSnd(long* ptr,long val)
{
	int cmpt=0;

	while ((*ptr!=val) && (cmpt<10))
	{
		cmpt++;
		Sleep(100);
	}

	if (*ptr==val) 
		return;
	else
	{
		SND_fn_vDisplayError(E_uwSndThreadDeadLockError,"");
	}
}

void SND_fn_vInitCriticalSectionForErrorDisplay(void)
{
	InitializeCriticalSection(&CriticalSectionForError);
	hEventForError=CreateEvent(NULL,TRUE,FALSE,NULL);
}

void SND_fn_vDesInitCriticalSectionForErrorDisplay(void)
{
	HANDLE tmp_hEventForError=hEventForError;

	LeaveCriticalSection(&CriticalSectionForError);
	DeleteCriticalSection(&CriticalSectionForError);
	hEventForError=NULL;
	ResetEvent(tmp_hEventForError);
	CloseHandle(tmp_hEventForError);
}

void SND_CALL SND_fn_vEnterCriticalSectionForErrorDisplay(void)
{
	EnterCriticalSection(&CriticalSectionForError);
	SetEvent(hEventForError);
	SND_fn_vMouchardThreadsndEvent(__FILE__,__LINE__);
}

void SND_CALL SND_fn_vQuitCriticalSectionForErrorDisplay(void)
{
	ResetEvent(hEventForError);
	LeaveCriticalSection(&CriticalSectionForError);
}


//attente semi-bloquante spécifique aux fonction Release/RestoreDriver
SndBool SND_CALL SND_fn_bEnterCriticalSectionForDriverThreadSnd(void)
{
	DWORD dwRet;
	DWORD delay;
//	static BOOL bReentraceDetected=FALSE;
    char texte[256];
	HANDLE a_hMutex[2];

    if (!g_bInitDone)
        SND_fn_vDisplayError(E_uwSndInitNotDoneError,"");
      
    if (g_hMutexSnd!=NULL && hEventForError!=NULL)
	{
        sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling);

		//tentative de lock avec abandon si le mutex de DisplayError se libère
		delay=INFINITE;
		
		a_hMutex[0]=g_hMutexSnd;
		a_hMutex[1]=hEventForError;

		dwRet=WaitForMultipleObjects(2,a_hMutex,FALSE,delay);

		if (dwRet==WAIT_OBJECT_0)
		{//le mutex SND a pu être locké
			if (WaitForSingleObject(hEventForError,0)!=WAIT_OBJECT_0)
			{
				g_bAnOtherThreadWaiting=FALSE;
				g_bFlagSnd=TRUE;
				return TRUE;
			}
			else
			{//event DisplayError non dispnible
				ReleaseMutex(g_hMutexSnd);
				return FALSE;
			}
		}
		
		if (dwRet==WAIT_OBJECT_0+1)
		{//error en cours de display->abandon
			return FALSE;
		}

        return FALSE;
	}
	
	return FALSE;

/*
	DWORD dwRet;
	DWORD delay;
	static BOOL bReentraceDetected=FALSE;
    char texte[256];
	HANDLE a_hMutex[2];

    if (!g_bInitDone)
        SND_fn_vDisplayError(E_uwSndInitNotDoneError,"");
      
    if (g_hMutexSnd!=NULL && SND_g_hErrorDisplayMutexSnd!=NULL)
	{
        sprintf(texte,"Last call by thread %x (line %d of %s)",dwLastThreadCalling,szLastLineCalling,szLastFileCalling);

		//1°:tentative de lock non bloquante du mutex géné
		delay=0;
		
		dwRet=WaitForSingleObject(g_hMutexSnd,delay);

		if (dwRet==WAIT_OBJECT_0)
		{//Mutex SND locké avec succés: tout est OK
			g_bAnOtherThreadWaiting=FALSE;
			g_bFlagSnd=TRUE;
			return TRUE;
		}

		//2°:tentative de lock avec abandon si le mutex de DisplayError se libère
		delay=INFINITE;
		
		a_hMutex[0]=g_hMutexSnd;
		a_hMutex[1]=SND_g_hErrorDisplayMutexSnd;

		dwRet=WaitForMultipleObjects(2,a_hMutex,FALSE,delay);

		if (dwRet==WAIT_OBJECT_0)
		{//le mutex SND a pu être locké
			ReleaseMutex(SND_g_hErrorDisplayMutexSnd);
			g_bAnOtherThreadWaiting=FALSE;
			g_bFlagSnd=TRUE;
			return TRUE;
		}
		
		if (dwRet==WAIT_OBJECT_0+1)
		{//error en cours de display->abandon
			return FALSE;
		}

        return FALSE;
	}
	
	return FALSE;
	*/
}

void __cdecl SND_fn_vCallbackThread( stThreadCallbackParam* pstParam)
{
	(*(pstParam->pfnFunction))(pstParam->lParam);

	SND_fn_vFreeSnd(pstParam);
}

//appel non bloquant d'une fonction de callback (création d'une thread si possible)
/*
void SND_CALL SND_fn_vStartCallbackThreadsnd(SND_td_pfn_vSoundCallback pfn,long param)
{
	stThreadCallbackParam* pstParam;

	pstParam=SND_fn_pvMallocSnd(sizeof(stThreadCallbackParam));
	pstParam->pfnFunction=pfn;
	pstParam->lParam=param;

#if defined(__WATCOMC__) && (__WATCOMC__<1100)
	_beginthread( SND_fn_vCallbackThread, NULL, 0, (void*)pstParam );
#else
	_beginthread( SND_fn_vCallbackThread, 0, (void*)pstParam );
#endif
}
*/