/*
	General NDTS SDK error generation/handler rules. Agreed by Mark, Daniel and Steve on 04/04/07.


	If a function does not use one of its parameters explicitly, then do not do validity check.
	This implies variable checking is done as late as possible, just before they are used. 

	If a function call fails then the client function is allowed to return just false and not call 
	handle error. If a function itself fails then error handler should be called at the point of 
	error and false is returned. 

	For internal functions, do asserts on parameters; assumes in release build that parameters are OK.
*/


#include <stdio.h>
#include <windows.h>
#include <crtdbg.h>
#include <shfolder.h>

#include "NDTSError.h"
#include "NDTSErrorInternal.h"

#define NDTS_ERROR_MAXIMUM_NUMBER_OF_LAST_ERROR_CODES 64


typedef struct {
	unsigned long	errorLogStringIndex ;
	char			errorLog[NDTS_MAXIMUM_NUMBER_OF_ERROR_LOG_STRINGS][NDTS_MAXIMUM_ERROR_STRING_LENGTH] ;
	unsigned long	numberOfErrorStrings ;
} NDTS_ErrorLog ;

typedef struct {
	DWORD			threadId ;
	int	lastErrorCode ;
} NDTS_ThreadLastErrorCode ;

typedef struct {
	NDTS_LogLevel file ; 
	NDTS_LogLevel app ; 
	NDTS_LogLevel debugger ;
} NDTS_LogLevels ;

NDTS_LogLevels logLevel;

NDTS_ErrorLog				errorLog ;
NDTS_ThreadLastErrorCode	lastErrorCodes[NDTS_ERROR_MAXIMUM_NUMBER_OF_LAST_ERROR_CODES] ;
// The number of populated entries in the array.
int			lastErrorCodesCount ;
int			lastErrorCode ;

void NDTS_SetThreadLastErrorCode(int errorCode) ;
int NDTS_GetThreadLastErrorCode() ;
int NDTS_FindThreadLastErrorCodeIndex(DWORD threadId) ;

// Turn off warning C4100 : unreferenced formal parameter for this function
#pragma warning(push)
#pragma warning(disable : 4100) 
 
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ 
	switch (ul_reason_for_call)
	{
		case DLL_PROCESS_ATTACH :
			NDTS_Error_DLL_Initialise() ;
			break ;
		case DLL_PROCESS_DETACH :
			NDTS_Error_DLL_Uninitialise() ;
			break ;
		case DLL_THREAD_ATTACH :
			break ;
		case DLL_THREAD_DETACH :
			break ;
    }

    return TRUE ;
}

#pragma warning(pop)

#define NOTE_BUFFER_SIZE 1000
void NDTS_LogLine(NDTS_LogLevel level, const char *msg, ...)
{
	va_list arg;
	char note[NOTE_BUFFER_SIZE];
	SYSTEMTIME systime;
	int pos, sizeForText;
	// Without the pair of lines below, we get a R6002 C run-time error - 
	// no floating point support loaded. Not sure why this happens...
	float test = 1.0f ;
	test = test ;
	GetSystemTime(&systime); 
	pos = sprintf(note, "%02d/%02d/%04d %02d:%02d:%02d.%03d ",    
		systime.wDay, systime.wMonth, systime.wYear,  
		systime.wHour, systime.wMinute, systime.wSecond, systime.wMilliseconds) ;

	sizeForText = NOTE_BUFFER_SIZE - (pos + 10);
	va_start(arg,msg);
	_vsnprintf(note+pos, sizeForText, msg, arg);
	va_end(arg);
	strcat(note, "\n");
	strcpy(&(note[NOTE_BUFFER_SIZE - 10]), "**TRNC\n");
	NDTS_LogSomeChars(level, note);
}


void NDTS_LogSomeChars(NDTS_LogLevel level, const char *note)
{
	if (level <= logLevel.debugger)
		OutputDebugString(note);

	if (level <= logLevel.file)
	{
		FILE *fp = fopen("c:\\log.txt", "a");
		if (!fp)
		{
			// Can't write to C:\ on vista
			static char szPath[1000] ;
			SHGetFolderPath	(0,                //hWnd	
				CSIDL_COMMON_APPDATA,   //csidl
				0,             //hToken
				0,			  //dwFlags
				(LPSTR)szPath) ;         //pszPath
			strcat(szPath, "\\NdtsSdkLog.txt");
			fp = fopen(szPath, "a");
		}
		if (fp)
		{
			fprintf(fp, note);
			fclose(fp);
		}
	}

	if (level <= logLevel.app)
	{
		int chars = (NDTS_MAXIMUM_ERROR_STRING_LENGTH - 1 > strlen(note)) ? strlen(note) : NDTS_MAXIMUM_ERROR_STRING_LENGTH - 2;
		strncpy(errorLog.errorLog[errorLog.errorLogStringIndex], note, chars) ;
		errorLog.errorLog[errorLog.errorLogStringIndex][chars] = 0;

		errorLog.errorLogStringIndex++ ;
		if (errorLog.errorLogStringIndex == NDTS_MAXIMUM_NUMBER_OF_ERROR_LOG_STRINGS)
			errorLog.errorLogStringIndex = 0 ;

		if (errorLog.numberOfErrorStrings < NDTS_MAXIMUM_NUMBER_OF_ERROR_LOG_STRINGS)
			errorLog.numberOfErrorStrings++ ;

	}
}

void NDTS_SetLogLevel(NDTS_LogLevel file, NDTS_LogLevel app, NDTS_LogLevel debugger)
{
	logLevel.file = file;
	logLevel.app = app;
	logLevel.debugger = debugger;
}

void NDTS_GetLogLevel(NDTS_LogLevel *pFile, NDTS_LogLevel *pApp, NDTS_LogLevel *pDebugger)
{
	*pFile = logLevel.file;
	*pApp = logLevel.app;
	*pDebugger = logLevel.debugger;
}

void NDTS_SetThreadLastErrorCode(int errorCode)
{
	DWORD threadId ;
	int index ;

	// Get the ID of the current thread.
	threadId = GetCurrentThreadId() ;

	// Try to find the current thread.
	index = NDTS_FindThreadLastErrorCodeIndex(threadId) ;

	if(index == -1)
		// The last error code array is full.
		lastErrorCode = errorCode ;
	else
	{
		lastErrorCodes[index].threadId = threadId ;
		lastErrorCodes[index].lastErrorCode = errorCode ;
	}
}

int NDTS_GetThreadLastErrorCode()
{
	DWORD threadId ;
	int index ;

	// Get the ID of the current thread.
	threadId = GetCurrentThreadId() ;

	// Try to find the current thread.
	index = NDTS_FindThreadLastErrorCodeIndex(threadId) ;

	if(index == -1)
		// The last error code array is full.
		return lastErrorCode ;
	else
		return lastErrorCodes[index].lastErrorCode ;
}

int NDTS_FindThreadLastErrorCodeIndex(DWORD threadId)
{
	int index ;

	// Return the array index of the current thread (if found).
	for(index = 0 ; index < lastErrorCodesCount; ++index)
		if(threadId == lastErrorCodes[index].threadId) 
			return index ;

	// Thread not found.
	return -1 ;
}

static NDTS_Boolean isInitialised = NDTS_BFalse;

void NDTS_InitErrorService()
{
	int lastErrorCodesIndex ;

	if (isInitialised)
		return;

	isInitialised = NDTS_BTrue;


	for(lastErrorCodesIndex = 0 ; lastErrorCodesIndex < NDTS_ERROR_MAXIMUM_NUMBER_OF_LAST_ERROR_CODES; ++lastErrorCodesIndex)
	{
		lastErrorCodes[lastErrorCodesIndex].threadId = 0 ;
		lastErrorCodes[lastErrorCodesIndex].lastErrorCode = 0 ;
	}

	lastErrorCodesCount = 0 ;
	lastErrorCode = 0 ;

	errorLog.errorLogStringIndex = 0 ;
	errorLog.numberOfErrorStrings = 0 ;
	logLevel.file = NDTS_LLNone;  // These are not the default values.  They are overriden by values in RQdefIntValues and further overridden by rq.ini entries, which are read in RQ_DLLInitialise()
	logLevel.app = NDTS_LLNone;
	logLevel.debugger = NDTS_LLNone;

}

void NDTS_HandleError(int errorCode)
{
	NDTS_HandleErrorWithLogLevel(errorCode, NDTS_LLErr);
}

void NDTS_HandleErrorWithLogLevel(int errorCode, NDTS_LogLevel logLevel)
{
	int strcmp1, strcmp2 ;
	const char *pString ;

	// Record the error code.
	NDTS_SetThreadLastErrorCode(errorCode) ;

	// Record the error string.
	pString = NDTS_GetCodeString(errorCode) ;
	// If the assert below goes off, it is likely that the error string is missing for this error code.
	_ASSERTE(memcmp("Unknown", pString, 8) != 0);
	if (memcmp("Unknown", pString, 8) == 0)
	{
		NDTS_LogLine(logLevel, "Missing error string for error code %x (hex)", errorCode);
	}

	strcmp1 = strcmp(pString, "DC_SetRequestEarlySampleData_NoEarlySampleData") ;
	strcmp2 = strcmp(pString, "DC_NoDataAvailable") ;
	if(strcmp1 != 0 && strcmp2 != 0)
		NDTS_LogLine(logLevel, pString);
}


int NDTS_GetLastError()
{
	return NDTS_GetThreadLastErrorCode() ;
}

void  NDTS_GetErrorDetail(int *pCode, const char **pString)
{
	int lastErrorCode ;

	lastErrorCode = NDTS_GetLastError() ;

	if (pCode != 0)
		*pCode = lastErrorCode ;

	// Mark
	pString ;
	//if (pString != 0)
	//	*pString = NDTS_GetCodeString(lastErrorCode) ;
}

// NDTS_DumpErrorLog isn't used anywhere (Dec 06).  The errorLog.ErrorLog array is used by VB test app and maybe other
// applications via NDTS_GetErrorLog() and NDTS_ReturnNumberOfErrorLogEntries()
NDTS_Boolean  NDTS_DumpErrorLog(const char *pFileName)
{
	char string[512] ;
	const char *fileName ;
	HANDLE fileHandle ;
	BOOL isWin32OK ;
	DWORD numBytesToWrite, numBytesWritten ;
	unsigned long i ;

	fileName = pFileName == 0 ? "NDTS_error_log.txt" : pFileName ;
	
	fileHandle = CreateFile(fileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0) ;

	if (fileHandle == INVALID_HANDLE_VALUE)
		return NDTS_BFalse ;

	for (i = 0 ; i < errorLog.numberOfErrorStrings; i++)
	{
		sprintf(string, "%s\n", errorLog.errorLog[(errorLog.errorLogStringIndex - i + NDTS_MAXIMUM_NUMBER_OF_ERROR_LOG_STRINGS) % NDTS_MAXIMUM_NUMBER_OF_ERROR_LOG_STRINGS]) ;

		numBytesToWrite = strlen(string) ;

		isWin32OK = WriteFile(fileHandle, (LPCVOID) string, numBytesToWrite, &numBytesWritten, 0) ;
		if (isWin32OK == FALSE || numBytesWritten != numBytesToWrite)
			return NDTS_BFalse ;
	}

	isWin32OK = CloseHandle(fileHandle) ;

	return isWin32OK == TRUE ? NDTS_BTrue : NDTS_BFalse ;
}

unsigned long NDTS_ReturnNumberOfErrorLogLines(void)
{
	return errorLog.numberOfErrorStrings;
}

NDTS_Boolean NDTS_GetErrorLogLine(unsigned long lineNumber, char *pMessageBuffer, unsigned long numberOfBytesInMessageBuffer) 
{
	int bytesToCopy = 0; 
	if (lineNumber >= errorLog.numberOfErrorStrings)
		return NDTS_BFalse;
	
	bytesToCopy = (numberOfBytesInMessageBuffer -1 > strlen(errorLog.errorLog[lineNumber]))
		? strlen(errorLog.errorLog[lineNumber]) 
		: numberOfBytesInMessageBuffer-1;

	strncpy(pMessageBuffer, errorLog.errorLog[lineNumber], bytesToCopy);
	pMessageBuffer[bytesToCopy] = 0;
	return NDTS_BTrue;
}

NDTS_Boolean NDTS_ClearErrorLog() 
{
	errorLog.errorLogStringIndex = 0;
	errorLog.numberOfErrorStrings = 0;
	return NDTS_BTrue;
}

NDTSERROR_API NDTS_Boolean CALL_CONV NDTS_RegisterGetCodeStringFunction(unsigned long sdkIndex, FPNDTS_GetCodeString fpGetCodeString)
{
	if (sdkIndex >= NDTS_ERROR_NUMBER_OF_SUPPORTED_SDKS)
		return NDTS_BFalse ;

	if (!isInitialised)
		NDTS_InitErrorService();

	globalState.pfpGetCodeStrings[sdkIndex] = fpGetCodeString ;

	return NDTS_BTrue ;
}

const char *NDTS_GetCodeString(int errorCode) 
{
	unsigned long sdkIndex = (errorCode >> 16) & 0xff ;
	static char *pUnknownString = "Unknown" ;

	return sdkIndex < NDTS_ERROR_NUMBER_OF_SUPPORTED_SDKS && 
		   globalState.pfpGetCodeStrings[sdkIndex] != 0 ? 
				(*globalState.pfpGetCodeStrings[sdkIndex])(errorCode) : pUnknownString ;
}
