#include "progwrp.h"
#include "export.h"
#include "implementations.h"

/*
	The InitOnce structure can be simplified.

	We have 2 bits to play around with (the rest will hold the value of lpContext).

	Bit 0 - indicates initialization is complete
	Bit 1 - indicates that initialization is asynchronous

	There is also a mutex and a critical section in use. It would be better if they were local to the 
	InitOnce object, but this seems to work very well. As long as the VLC InitOnce-related crashes
	in Supermium cease, it is good.
*/

#define INIT_COMPLETE 0x1
#define INIT_ASYNC    0x2

BOOL WINAPI Implementation_InitOnceBeginInitialize(
  LPINIT_ONCE lpInitOnce,
  DWORD       dwFlags,
  PBOOL       fPending,
  LPVOID      *lpContext
)
/*
	This function does not have an associated callback function,
	but it can be used with the INIT_ONCE object to initialize based on the object itself.
*/
{
	if(dwFlags & ~(INIT_ONCE_CHECK_ONLY | INIT_ONCE_ASYNC) ||
		!lpInitOnce || !fPending)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	if(((ULONG_PTR)lpInitOnce->Ptr & INIT_ASYNC) && !(dwFlags & INIT_ONCE_ASYNC))
		return FALSE;

	if(dwFlags & INIT_ONCE_ASYNC)
		InterlockedOr(&lpInitOnce->Ptr, INIT_ASYNC);

	if((ULONG_PTR)lpInitOnce->Ptr & INIT_COMPLETE)
	{
		*fPending = FALSE;
	}
	else
	{
		if(!(dwFlags & INIT_ONCE_CHECK_ONLY))
			InterlockedOr(&lpInitOnce->Ptr, INIT_COMPLETE);
		*fPending = TRUE;
		return TRUE;
	}

	if(lpContext && ((ULONG_PTR)lpInitOnce->Ptr & INIT_COMPLETE))
	{
		*lpContext = (ULONG_PTR)lpInitOnce->Ptr - ((ULONG_PTR)lpInitOnce->Ptr % 4);
	}

	return TRUE;
}

BOOL WINAPI Implementation_InitOnceExecuteOnce(
  PINIT_ONCE    InitOnce,
  PINIT_ONCE_FN InitFn,
  PVOID         Parameter,
  LPVOID        *Context
)
{
	BOOL Result = FALSE;

	if(InitFn && !InitOnce)
		return InitFn(InitOnce, Parameter, Context);

	if(!InitOnce || !InitFn)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	if((ULONG_PTR)InitOnce->Ptr & INIT_COMPLETE)
	{
		if(Context)
			*Context = (ULONG_PTR)InitOnce->Ptr - ((ULONG_PTR)InitOnce->Ptr % 4);

		return TRUE;
	}
	else
	{
		EnterCriticalSection(&SrwSharedCs);
		if(((ULONG_PTR)InitOnce->Ptr & INIT_COMPLETE) || Result)
			goto Result;
		Result = InitFn(InitOnce, Parameter, Context);
		
Result:
		if(Result)
		{
			(ULONG_PTR)InitOnce->Ptr = (ULONG_PTR)Context | ((ULONG_PTR)InitOnce->Ptr & 0x3);
			InterlockedOr(&InitOnce->Ptr, INIT_COMPLETE);
            LeaveCriticalSection(&SrwSharedCs);
			return Result;
		}
		else
		{
            LeaveCriticalSection(&SrwSharedCs);
			if ((ULONG_PTR)InitOnce->Ptr & INIT_COMPLETE)
				return TRUE;
			return Result;
		}
	}
}

BOOL WINAPI Implementation_InitOnceComplete(
  LPINIT_ONCE lpInitOnce,
  DWORD       dwFlags,
  LPVOID      lpContext
)
{
	if(!lpInitOnce || dwFlags & ~(INIT_ONCE_INIT_FAILED | INIT_ONCE_ASYNC))
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		return FALSE;
	}

	if(dwFlags & INIT_ONCE_INIT_FAILED)
	{
		(ULONG_PTR)lpInitOnce->Ptr = 0;
		return TRUE;
	}

	InterlockedOr(&lpInitOnce->Ptr, INIT_COMPLETE);

	if(lpContext)
	{
		InterlockedOr(&lpInitOnce->Ptr, lpContext);
	}

	return TRUE;
}

void WINAPI Implementation_InitOnceInitialize(
  PINIT_ONCE InitOnce
)
{
	InitOnce->Ptr = 0;
}