///////////////////////////////////////////////////////////////////////////////////////
// April 2006 - Separate data collection thread to support SFD, WinRS, WinRS - Saab.
// See RQThread.h for details

#include <windows.h>
#include <stdio.h>
#include <io.h>
#include <crtdbg.h>
#include "RQ.h"
#include "RQProtected.h"
#include "RQThreadInternal.h"
#include "RQSnapshotInternal.h"
#include "RQTErrorInternal.h"
// NDTSErrorInternal.h must be included after RQTError.h so that RQT_ReturnCode has been defined.
#include "NDTSError.h"

RQ_API RQT_State *pThreadStates = 0; // One per card - like pBoardStates in the base sdk.  There will probably be one thread for each card
unsigned long activeThreads = 0;


RQ_API NDTS_Boolean RQT_Initialise(unsigned long cardIndex)
{
	RQT_State *pThreadState ;
	char tempStr[100] ;
	DWORD dwThreadId ;

	if (cardIndex >= RQ_ReturnNumberOfRapiDAQBoards()) 
		return NDTS_BFalse;

	if (activeThreads++ == 0)
	{
		NDTS_RegisterGetCodeStringFunction(NDTS_ERROR_RQT_SDK_INDEX, &RQT_GetCodeString) ;

		if (pThreadStates != 0)
		{
			RQT_HandleError(RQT_Initialise_AlreadyInititialised);
			return NDTS_BFalse;
		}

		pThreadStates = malloc(RQ_ReturnNumberOfRapiDAQBoards() * sizeof(RQT_State));
		if (pThreadStates == 0)
		{
			RQT_HandleError(RQT_OutOfMemory);
			return NDTS_BFalse;
		}
	}

	pThreadState = &pThreadStates[cardIndex];

	if (!RQ_SetUpdateSampleParametersHook(cardIndex, RQT_UpdateParameters)) 
	{
		if (!RQT_HandleRQError()) 
			return NDTS_BFalse;
	}

	// Assume interrupts don't work by setting timeout to fast.  If we get an interrupt, set timeout to slow.
	pThreadState->plxWaitTimeout		= RQT_PLX_TIMEOUT_FAST ;
	pThreadState->threadBankSwaps		= 0 ;
	pThreadState->appBankSwaps			= 0 ;
	pThreadState->currentThreadState	= RQT_SMSNotStarted ;
	pThreadState->requestedThreadState	= RQT_SMSNotStarted ;
	pThreadState->collectState.rawScansInitialised = 0;

	// Fake PLX Semaphore - to be replaced when getting interrupts working
	// Created with us as initial owner to simulate the interrupt never going off in the thread
	sprintf(tempStr, "RQT_PlxSemaphore%d", cardIndex);
	pThreadState->plxSemaphore = CreateSemaphore(NULL, 0, 1, tempStr); // Initial count 0 - block on next wait. Max count of 1
	if (!pThreadState->plxSemaphore) 
	{
		RQT_HandleError(RQT_Initialise_FailedToCreatePLXInterrupt);
		return NDTS_BFalse;
	}

	// Semaphore that is released each time the thread is run
	sprintf(tempStr, "RQT_ThreadHasRunSemaphore%d", cardIndex);
	pThreadState->threadHasRun = CreateSemaphore(NULL, 0, 1, tempStr); // Initial count 0 - block on next wait. Max count of 1
	if (!pThreadState->threadHasRun) 
	{
		RQT_HandleError(RQT_OutOfMemory);
		return NDTS_BFalse;
	}

	InitializeCriticalSection(&(pThreadState->threadCriticalSection)) ;
	
	pThreadState->currentThreadState = RQT_SMSNotStarted ;

	{
		HANDLE hThread ;
		static int staticCardIndex;
		int i,j;

		staticCardIndex = cardIndex;
		hThread = CreateThread(0, 0, RQT_ThreadMain, (void *)&staticCardIndex, 0, &dwThreadId) ;
		if (!hThread)
		{
			RQT_HandleError(RQT_OutOfMemory);
			return NDTS_BFalse;
		}

//		i = SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST) ;
		i = SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL) ;
		j=1;
		j++;
	}

	return NDTS_BTrue;
}

RQ_API NDTS_Boolean RQT_Uninitialise(unsigned long cardIndex)
{
	RQT_State *pThreadState = RQT_ReturnThreadStatePointer(cardIndex) ;
	if (!pThreadState) 
		return NDTS_BFalse; 

	RQT_SetThreadState(cardIndex, RQT_SMSShutdown) ;

	DeleteCriticalSection(&(pThreadState->threadCriticalSection));

	if (--activeThreads == 0)
	{
		free(pThreadStates);
		pThreadStates = 0;
	}

	return NDTS_BTrue;
}


RQ_API NDTS_Boolean CALL_CONV RQT_SetThreadState(unsigned long cardIndex, RQT_StateMachineState newState)
{
	RQT_State *pThreadState;
	unsigned long retVal;
	RQ_State *pBoardState = RQ_ReturnBoardStatePointer(cardIndex) ;
	if (!pBoardState) return NDTS_BFalse; // Checks number of cards is OK
	pThreadState= &pThreadStates[cardIndex];

	if (newState != RQT_SMSSnapshot && newState != RQT_SMSCollect && newState != RQT_SMSPause && newState != RQT_SMSShutdown)
	{
		RQT_HandleError(RQT_SetThreadState_InvalidState);
		return NDTS_BFalse;
	}
	
	if (pThreadState->currentThreadState == RQT_SMSShutdown)
	{
		RQT_HandleError(RQT_ThreadHasShutdown);
		return NDTS_BFalse;
	}

	if (pBoardState->lockEntryCount != 0)
	{
		RQT_HandleError(RQT_SetThreadState_MustNotBeCalledInsideRQLock);
		return NDTS_BFalse;
	}

	EnterCriticalSection(&(pThreadState->threadCriticalSection)); // Block until the thread is in the waiting section. 
	pThreadState->requestedThreadState = newState;
	LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 

	// Need two WaitForSingleObject calls.  The thread may have just set the semaphore to empty so the
	// first call sets the semaphore to full and the 
	// second one blocks until the thread has done a release.
	retVal = WaitForSingleObject(pThreadState->threadHasRun, RQT_TIME_TO_WAIT_FOR_THREAD);
	if (retVal != WAIT_OBJECT_0)
	{
		RQT_HandleError(RQT_SetThreadState_TimeoutWaitingForThread);
		RQ_Unlock(cardIndex);
		return NDTS_BFalse;
	}

	retVal = WaitForSingleObject(pThreadState->threadHasRun, RQT_TIME_TO_WAIT_FOR_THREAD);
	if (retVal != WAIT_OBJECT_0)
	{
		RQT_HandleError(RQT_SetThreadState_TimeoutWaitingForThread);
		RQ_Unlock(cardIndex);
		return NDTS_BFalse;
	}

	if (pThreadState->currentThreadState != newState)
	{
		RQT_HandleError(RQT_SetThreadState_ThreadFailedToAcknowledgeNewState);
		RQ_Unlock(cardIndex);
		return NDTS_BFalse;
	}
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV RQT_BankSwapAndUpdate(unsigned long cardIndex)
{
	unsigned long currentBankSwaps = 0, value = 0;
	NDTS_Boolean isOK;
	int timeoutCounter = 0;
	NDTS_Boolean isDataAvailable = NDTS_BFalse;
	RQT_State *pThreadState = RQT_ReturnThreadStatePointer(cardIndex) ;
	
	if (!pThreadState) 
		return NDTS_BFalse; 

	if (pThreadState->currentThreadState == RQT_SMSShutdown)
	{
		RQT_HandleError(RQT_ThreadHasShutdown);
		return NDTS_BFalse;
	}

	EnterCriticalSection(&(pThreadState->threadCriticalSection)); 
	currentBankSwaps = pThreadState->threadBankSwaps;

	// Check for one bank already full because the bank may have filled between the thread running and the app calling this function.
	RQ_Lock(cardIndex);
	isOK = RQ_GetDataSamplingStatus(cardIndex, &value);
	if (!isOK)
	{
		RQ_Unlock(cardIndex);
		LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
		return NDTS_BFalse ;
	}

	// No banks full - request a swap from the firmware
	if ((value & 0x01) == 0) 
	{
		isOK = RQ_SetRequestEarlySampleData(cardIndex) ;
		// Check for OneBankAlreadyFull due to a bank filling between the check above and the call to RQ_SetRequestEarlySampleData()
		if (!isOK && RQ_GetLastError() != RQ_SetRequestEarlySampleData_OneBankAlreadyFull) 
		{
			if (RQ_GetLastError() ==  RQ_SetRequestEarlySampleData_NoEarlySampleData)
			{
				// Return an error here because there is no data to fetch and so we don't do a bank swap
				RQT_HandleError(RQT_NoDataAvailable);
				RQ_Unlock(cardIndex);
				LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
				return NDTS_BFalse ;
			}
			else
			{
				// Carry on if there is a non fatal error (e.g. a CRC)
				if (!RQT_HandleRQError())
				{
					RQ_Unlock(cardIndex);
					LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
					return NDTS_BFalse ;
				}
			}
		}
	}

	// Wait for the firmware to finish the scan it is on and set data available
	do
	{
		if (timeoutCounter++ > 1000000)
		{
			RQT_HandleError(RQT_BankSwapAndUpdate_TimeOutWaitingForBankSwap);
			RQ_ResetFirmware(cardIndex);
			RQ_Unlock(cardIndex);
			LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
			return NDTS_BFalse ;
		}

		isOK = RQ_GetIsSampleDataAvailable(cardIndex, &isDataAvailable) ;
		if (!isOK && !RQT_HandleRQError())
		{
			RQ_Unlock(cardIndex);
			LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
			return NDTS_BFalse;
		}
	}
	while (!isDataAvailable);

	// Get the data from the bank
	if (!RQT_ServiceRapiDAQ(cardIndex, SRQApp))
	{
		RQ_Unlock(cardIndex);
		LeaveCriticalSection(&(pThreadState->threadCriticalSection));
		return NDTS_BFalse;
	}

	RQ_Unlock(cardIndex);
	LeaveCriticalSection(&(pThreadState->threadCriticalSection)); 
	return NDTS_BTrue;
}

// Thread entry point
static DWORD WINAPI RQT_ThreadMain(LPVOID lpParameter)
{
	// Data capture thread function
	unsigned long *pParam = (unsigned long *)lpParameter;
	unsigned long cardIndex = *pParam;
	unsigned long threadLoops = 0;
	NDTS_Boolean isOK;
	RQT_State *pThreadState= &pThreadStates[cardIndex];
	RQ_State *pBoardState = RQ_ReturnBoardStatePointer(cardIndex) ;

	do
	{
		EnterCriticalSection(&(pThreadState->threadCriticalSection)) ;
		pThreadState->currentThreadState = pThreadState->requestedThreadState;

		if (pThreadState->currentThreadState == RQT_SMSSnapshot || pThreadState->currentThreadState == RQT_SMSCollect)
		{
			threadLoops++;
			sprintf(pBoardState->ppDiagnosticStrings[1], "Thread loops %d", threadLoops);

			RQ_Lock(cardIndex);
			isOK = RQT_ServiceRapiDAQ(cardIndex, SRQThread);
			if (!isOK)
			{
				RQ_Unlock(cardIndex);
				LeaveCriticalSection(&(pThreadState->threadCriticalSection));
				goto breakout ;
			}
			RQ_Unlock(cardIndex);
		}
		if (!ReleaseSemaphore(pThreadState->threadHasRun, 1, NULL))
		{
		    DWORD dw = GetLastError(); 
			if (dw != ERROR_TOO_MANY_POSTS) // Too many posts if no-one was waiting for us
			{
				RQT_HandleError(RQT_ThreadMain_ReleaseSemaphoreReturnedError);
				goto breakout;
			}
		}
		LeaveCriticalSection(&(pThreadState->threadCriticalSection));

		// Yeild the thread in a state dependent way
		if(!RQT_YieldThread(cardIndex))
			goto breakout ;
	}
	while (pThreadState->currentThreadState != RQT_SMSShutdown);

	pThreadState->threadExitCode = RQT_GetLastError();

breakout:
	pThreadState->currentThreadState = RQT_SMSShutdown;
	if (!ReleaseSemaphore(pThreadState->threadHasRun, 1, NULL))
		RQT_HandleError(RQT_ThreadMain_ReleaseSemaphoreReturnedError);

	return 0;
}

NDTS_Boolean RQT_YieldThread(unsigned long cardIndex)
{
	RQT_State *pThreadState = &pThreadStates[cardIndex];
	unsigned long retVal;

	switch(pThreadState->currentThreadState)
	{
	case RQT_SMSSnapshot:
	case RQT_SMSCollect:
		retVal = WaitForSingleObject(pThreadState->plxSemaphore, pThreadState->plxWaitTimeout);
		if (retVal == WAIT_OBJECT_0)
		{
			// If the PLX hasn't generated any interrupts yet, plxWaitTimeout is set to fast
			// in case the hardware doesn't support interrupts.  
			// Once PLX has generated an interrupt, set the timeout to slow from now on to 
			// reduce load on the processor.
			pThreadState->plxWaitTimeout = RQT_PLX_TIMEOUT_SLOW;
		} 
		else if (retVal != WAIT_TIMEOUT)
		{
			RQT_HandleError(RQT_ThreadMain_ReleaseSemaphoreReturnedError);
			return NDTS_BFalse ;
		}
		break;
	case RQT_SMSPause:
		Sleep(RQT_PAUSE_TIME);
		break;
	case RQT_SMSNotStarted:
		Sleep(1);  // Poll quickly but yeild regularly for a faster startup.
		break;	
	}

	return NDTS_BTrue ;
}

RQT_State *RQT_ReturnThreadStatePointer(unsigned long cardIndex) 
{
	if (cardIndex >= RQ_ReturnNumberOfRapiDAQBoards()) 
	{
		RQT_HandleError(RQ_ReturnBoardStatePointer_InvalidCardIndex) ;
		return 0;
	}
	return &pThreadStates[cardIndex] ;
}


void RQT_UpdateParameters(unsigned long cardIndex)
{
	RQT_State *pThreadState = RQT_ReturnThreadStatePointer(cardIndex) ;
	if (!pThreadState) return ; 

	switch(pThreadState->currentThreadState)
	{
	case RQT_SMSSnapshot:
		RQT_UpdateSnapshotSampleStorage(cardIndex);
		break;
	case RQT_SMSCollect:
		RQT_UpdateParametersCollectHook(cardIndex);
		break;
	}
}


NDTS_Boolean RQT_UpdateDataSamplingStats(unsigned long cardIndex)
{
	NDTS_Boolean	isDataAvailable, 
		isSomeDataSampled, 
		hasNewTriggerOccured, 
		hasTriggerOccurredBeforeProcessComplete, 
		isDataFullError;

	static unsigned long someDataSampled = 0;
	static unsigned long newTriggerOccurred = 0;
	static unsigned long triggerOccurredBeforeDownloadComplete = 0;
	static unsigned long dataFullError = 0;
	static unsigned long dataAvailable = 0;
	RQ_State *pBoardState = RQ_ReturnBoardStatePointer(cardIndex) ;

	if (!RQ_GetSamplingStatus(cardIndex, 
		&isDataAvailable, 
		&isSomeDataSampled, 
		&hasNewTriggerOccured, 
		&hasTriggerOccurredBeforeProcessComplete, 
		&isDataFullError)) 
		return NDTS_BFalse;

	// zero bank swaps
	// one thread loops
	if(isDataAvailable)
	{
//		OutputDebugString("dataAvailable\n");
//		sprintf(pBoardState->ppDiagnosticStrings[4], "data avail %d", ++dataAvailable);
		// This gives same reading as the same as the bank swaps counter
	}

	if(isSomeDataSampled)	
	{
//		OutputDebugString("someDataSampled\n");
		sprintf(pBoardState->ppDiagnosticStrings[2], "isSomeDataSampled %d", ++someDataSampled);
	}

	if(hasNewTriggerOccured)
	{
//		OutputDebugString("newTriggerOccured\n");
	}

	if(hasTriggerOccurredBeforeProcessComplete)
	{
//		OutputDebugString("triggerOccurredBeforeDownloadComplete\n");
// using 3 for noise filter samples		sprintf(pBoardState->ppDiagnosticStrings[3], "triggerOccurredBeforeDownloadComplete %d", ++triggerOccurredBeforeDownloadComplete);
	}

	if(isDataFullError)	
	{
		int i;
		static int doSpecialRecovery = 0;
		unsigned long repRate = 0;
		NDTS_Boolean isOk;
		sprintf(pBoardState->ppDiagnosticStrings[4], "dataFullError %d", ++dataFullError);
		//filelogt("Data Full Error %d\n", dataFullError);

		if (doSpecialRecovery)
		{
			for(i=0;i<10;i++)
			{
				Sleep(10);
				RQ_ResetFirmware(cardIndex);
				Sleep(10);
			}
		}

		// On a data full, need to read both banks of memory.  
		// Send bank read signal twice to the firmware, will replace the sleeps with DMA data reads when it is all working.
		// Sept 06 - set rep rate slow  whilst resetting so free running does not fill again during the reset.
		// Max period is 131072 so set slow rate to 200Hz
		//Adjusted sleeps from 100 to 10.

		// Also call RQ_ResetFirmware after the two downloads to ensure the firmware is fully reset
		isOk = RQ_GetRepetitionRate(cardIndex, &repRate) ;
		_ASSERTE(isOk); isOk;
		isOk = RQ_SetRepetitionRate(cardIndex, 200) ;
		_ASSERTE(isOk); isOk;

		Sleep(10);
		isOk = RQ_IndicateSampleDataDownloaded(cardIndex) ;
		_ASSERTE(isOk); isOk;
		Sleep(10);
		isOk = RQ_IndicateSampleDataDownloaded(cardIndex) ;
		_ASSERTE(isOk); isOk;
		isOk = RQ_ResetFirmware(cardIndex);
		_ASSERTE(isOk); isOk;
		isOk = RQ_SetRepetitionRate(cardIndex, repRate) ;
		_ASSERTE(isOk); isOk;
	}
	return NDTS_BTrue;
}
// Check for data available and service the RapiDAQ card if it is.
// threadCriticalSection and RQ_Lock() must both be taken (in that order) before this is called.
NDTS_Boolean RQT_ServiceRapiDAQ(unsigned long cardIndex, enum SRQCaller caller)
{
	RQT_State *pThreadState= &pThreadStates[cardIndex];
	RQ_State *pBoardState = RQ_ReturnBoardStatePointer(cardIndex) ;

	NDTS_Boolean isDataAvailable = NDTS_BFalse;
	NDTS_Boolean isOK = NDTS_BFalse;

	isOK = RQT_UpdateDataSamplingStats(cardIndex);
	if (!isOK && !RQT_HandleRQError()) 
		return NDTS_BFalse;

	isOK = RQ_GetIsSampleDataAvailable(cardIndex, &isDataAvailable) ;
	if (!isOK && !RQT_HandleRQError())
		return NDTS_BFalse;

	if (isDataAvailable) // Bank swap detected
	{
		switch(pThreadState->currentThreadState)
		{
		case RQT_SMSSnapshot:
			isOK = RQT_UpdateSnapshot(cardIndex);
			if (!isOK) 	
				return NDTS_BFalse;
			break;
		case RQT_SMSCollect:	
			isOK = RQT_UpdateCollect(cardIndex);
			if (!isOK) 	
				return NDTS_BFalse;
			break;
		}
		switch(caller)
		{
		case SRQThread:
			pThreadState->threadBankSwaps++;
			break;
		case SRQApp:
			pThreadState->appBankSwaps++;
			break;
		default:
			_ASSERTE(0);
		}
		sprintf(pBoardState->ppDiagnosticStrings[0], "Swaps: thread %d, app %d", pThreadState->threadBankSwaps, pThreadState->appBankSwaps);
	}
	return NDTS_BTrue;
}

RQ_API void RQT_GetLogLevel(NDTS_LogLevel *pFile, NDTS_LogLevel *pApp, NDTS_LogLevel *pDebugger)
{
	NDTS_GetLogLevel(pFile, pApp, pDebugger);
}

RQ_API void RQT_SetLogLevel(NDTS_LogLevel file, NDTS_LogLevel app, NDTS_LogLevel debugger)
{
	NDTS_SetLogLevel(file, app, debugger);
}
