#include <stdio.h>
#include <io.h>
#include <windows.h>
#include <stdarg.h>
#include <crtdbg.h>

#include "NDTSEnums.h"
#include "NDTSError.h"
#include "NDTSCommonHAL.h"

#include "PRX.h"
#include "PRXInternal.h"
#include "PRXProtected.h"
#include "PRXStrings.h"


// This is global so that a function can check if a card number is valid
unsigned long prxs_found = 0;

PRX_State *pRX_StateArray = NULL;


NDTS_Boolean CALL_CONV PRX_OpenDMAChannel(unsigned long card)
{
	PRX_State *pRX_State;
	NDTS_Boolean isOK ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (!pRX_State->isPciInitialised)
	{
		PRX_HandlePRXError(PRX_OpenDMAChannel_IspciInitialisedFail) ;
		return NDTS_BFalse ;
	}
	
	if (pRX_State->isDMAChannelOpen)
	{
		PRX_HandlePRXError(PRX_OpenDMAChannel_DMAChannelAlreadyOpen) ;
		return NDTS_BFalse ;
	}

	isOK = NDTS_HAL_OpenDMAChannel(NDTS_SIDPRX, card) ;
	if (!isOK)
		return NDTS_BFalse ;

	pRX_State->isDMAChannelOpen = NDTS_BTrue ;
	return NDTS_BTrue ;
}

NDTS_Boolean CALL_CONV PRX_CloseDMAChannel(unsigned long card)
{
	PRX_State *pRX_State;
	NDTS_Boolean isOK ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (!pRX_State->isPciInitialised)
	{
		PRX_HandlePRXError(PRX_CloseDMAChannel_IspciInitialisedFail) ;
		return NDTS_BFalse ;
	}

	if (!pRX_State->isDMAChannelOpen)
	{
		PRX_HandlePRXError(PRX_CloseDMAChannel_IsDMAChannelOpenFail) ;
		return NDTS_BFalse ;
	}

	isOK = NDTS_HAL_CloseDMAChannel(NDTS_SIDPRX, card) ;
	if (!isOK)
		return NDTS_BFalse ;

	pRX_State->isDMAChannelOpen = NDTS_BFalse ;

	return NDTS_BTrue ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_SetCheckData(unsigned long card, unsigned long data)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	return PRX_SetSystemRegister(card, PRX_CHECK_REGISTER, data) ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_GetCheckData(unsigned long card, unsigned long *value)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	return PRX_GetRegister(card, PRX_CHECK_REGISTER, value) ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_SetWriteEEPROM(unsigned long card)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	return PRX_SetSystemRegister(card, PRX_SYSTEM_COMMAND_REGISTER, 0x00000002) ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_SetReadEEPROM(unsigned long card)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	return PRX_SetSystemRegister(card, PRX_SYSTEM_COMMAND_REGISTER, 0x00000004) ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_GetReadEEPROM(unsigned long card, unsigned long *flag)
{
	unsigned long value ;
	NDTS_Boolean isOK ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	isOK = PRX_GetSystemRegister(card, PRX_SYSTEM_STATUS_REGISTER, &value) ;
	if (!isOK)
		return NDTS_BFalse ;

	*flag = (value & 0x00000004) != 0 ? 1 : 0 ;

	return NDTS_BTrue ;
}

NDTS_Boolean CALL_CONV PRX_AddDiagnosticString(unsigned long card, unsigned long registerOffset, unsigned long registerValue)
{
	HANDLE fileHandle ;
	BOOL isWin32OK ;
	DWORD numBytesToWrite, numBytesWritten ;
	char string[512] ;
	PRX_State *pRX_State ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	sprintf(string, "0x%04x:0x%08x\r\n", registerOffset, registerValue) ;

	fileHandle = CreateFile(pRX_State->diagnosticsFilename, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ;
	if (fileHandle == INVALID_HANDLE_VALUE)
		return NDTS_BFalse ;

	SetFilePointer(fileHandle, 0, NULL, FILE_END) ;

	numBytesToWrite = strlen(string) ;

	isWin32OK = WriteFile(fileHandle, (LPCVOID) string, numBytesToWrite, &numBytesWritten, NULL) ;
	if (isWin32OK == FALSE || numBytesWritten != numBytesToWrite)
		goto Error ;

	isWin32OK = CloseHandle(fileHandle) ;
	if (isWin32OK == FALSE)
		return NDTS_BFalse ;

	return NDTS_BTrue ;

Error:
	CloseHandle(fileHandle) ;
	return NDTS_BFalse ;
}

PRX_API NDTS_Boolean CALL_CONV PRX_SetRegister(unsigned long card, unsigned long offset, unsigned long value)
{
	NDTS_Boolean isOK ;
	PRX_State *pRX_State;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (!pRX_State->isPciInitialised)
	{
		PRX_HandlePRXError(PRX_SetRegister_IspciInitialisedFail) ;
		return NDTS_BFalse ;
	}

	isOK = (offset + PRX_REGISTERS_START_OFFSET) < PRX_REGISTERS_END_OFFSET ;
	if (isOK)
	{
		if (pRX_State->isDiagnosticsMode)
			PRX_AddDiagnosticString(card, offset, value) ;

		isOK = NDTS_HAL_SetWord(NDTS_SIDPRX, card, PRX_REGISTERS_START_OFFSET + offset, value) ;
		if (!isOK)
			return NDTS_BFalse ;
	}
	else
	{
		PRX_HandlePRXError(PRX_SetRegister_OffsetOutOfRange) ;
		return NDTS_BFalse ;
	}

	return NDTS_BTrue ;
}

PRX_API NDTS_Boolean CALL_CONV PRX_GetRegister(unsigned long card, unsigned long offset, unsigned long *pValue)
{
	NDTS_Boolean isOK ;
	PRX_State *pRX_State;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (!pRX_State->isPciInitialised)
	{
		PRX_HandlePRXError(PRX_GetRegister_IspciInitialisedFail) ;
		return NDTS_BFalse ;
	}

	isOK = (offset + PRX_REGISTERS_START_OFFSET) < PRX_REGISTERS_END_OFFSET ;
	if (!isOK)
	{
		PRX_HandlePRXError(PRX_GetRegister_OffsetOutOfRange) ;
		return NDTS_BFalse ;
	}
	else
	{
		return NDTS_HAL_GetWord(NDTS_SIDPRX, card, PRX_REGISTERS_START_OFFSET + offset, pValue) ;
	}
}


/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_SetSystemRegister(unsigned long card, unsigned long offset, unsigned long value)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	if (offset >= PRX_SYSTEM_FPGA_REGISTERS_END_OFFSET - PRX_SYSTEM_FPGA_REGISTERS_START_OFFSET)
	{
		PRX_HandlePRXError(PRX_SetSystemRegister_OutOfRange) ;
		return NDTS_BFalse ;
	}

	return PRX_SetRegister(card, PRX_SYSTEM_FPGA_REGISTERS_START_OFFSET + offset, value) ;
}

/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_GetSystemRegister(unsigned long card, unsigned long offset, unsigned long *value)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	if (offset >= PRX_SYSTEM_FPGA_REGISTERS_END_OFFSET - PRX_SYSTEM_FPGA_REGISTERS_START_OFFSET)
	{
		PRX_HandlePRXError(PRX_GetSystemRegister_OutOfRange) ;
		return NDTS_BFalse ;
	}

	return PRX_GetRegister(card, offset, value) ;
}




NDTS_Boolean CALL_CONV PRX_GetSRAMLong(unsigned long card, unsigned long offset, unsigned long *pValue)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	if (offset > PRX_REGISTERS_END_OFFSET)
	{
		PRX_HandlePRXError(PRX_GetSRAMLong_OffsetOutOfRange) ;
		return NDTS_BFalse ;
	}

	return NDTS_HAL_GetWord(NDTS_SIDPRX, card, offset, pValue) ;
}

NDTS_Boolean CALL_CONV PRX_SetSRAMLong(unsigned long card, unsigned long offset, unsigned long value)
{
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	if (offset > PRX_REGISTERS_END_OFFSET)
	{
		PRX_HandlePRXError(PRX_GetSRAMLong_OffsetOutOfRange) ;
		return NDTS_BFalse ;
	}

	return NDTS_HAL_SetWord(NDTS_SIDPRX, card, offset, value) ;
}

NDTS_Boolean CALL_CONV PRX_SystemRAMTest(unsigned long card)
{
	NDTS_Boolean isOK ;
	unsigned long nCount, i, pLocalAddress ;
	unsigned long *pData ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	isOK = PRX_OpenDMAChannel(card) ;
	
	if (!isOK)
		return NDTS_BFalse ;

	nCount = 0x40000 ;
	pData = malloc(sizeof(unsigned long) * nCount) ;
	//pData = new U32[nCount] ;

	pLocalAddress = PRX_SYSTEM_RAM_OFFSET ;

	for (i = 0 ; i < nCount ; i++)
		pData[i] = i ;

	isOK = NDTS_HAL_PerformDMATransfer(NDTS_SIDPRX, card, (void *) pData, pLocalAddress, nCount << 2, NDTS_BFalse) ;
	if (!isOK)
		return NDTS_BFalse ;

	for (i = 0 ; i < nCount ; i++)
		pData[i] = 0xffffffff ;

	isOK = NDTS_HAL_PerformDMATransfer(NDTS_SIDPRX, card, (void *) pData, pLocalAddress, nCount << 2, NDTS_BTrue) ;
	if (!isOK)
		return NDTS_BFalse ;

	i = 0 ; 

	do
	{
		if (pData[i] != i)
			isOK = NDTS_BFalse ;

		i++ ;
	}
	while (i < nCount && isOK) ;

	if (!isOK)
		return NDTS_BFalse ;

	isOK = PRX_CloseDMAChannel(card) ;
	if (!isOK)
		return NDTS_BFalse ;

	free(pData) ;
//	delete[] pData ;

	return isOK ;
}


/*PRX_API*/ NDTS_Boolean CALL_CONV PRX_SystemFPGAInversionTest(unsigned long card)
{
	unsigned long value, value2 ;
	NDTS_Boolean isOK ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	value = 0x12345678 ;

	PRX_SetCheckData(card, value) ;
	isOK = PRX_GetCheckData(card, &value2) ;

	return isOK ? value == ~value2 ? NDTS_BTrue : NDTS_BFalse : NDTS_BFalse ;
}

void PRX_HandlePRXError(PRX_ReturnCode errorCode) 
{
	NDTS_HandleError(errorCode);
}


/*PRX_API*/ void CALL_CONV PRX_CreateCriticalSection(unsigned long card)
{
	if (PRX_CheckState(card))
	{
		PRX_State *pRX_State = &pRX_StateArray[card];
		InitializeCriticalSection(&pRX_State->criticalSection) ;
	}
}

/*PRX_API*/ void CALL_CONV PRX_DeleteCriticalSection(unsigned long card)
{
	if (PRX_CheckState(card))
	{
		PRX_State *pRX_State = &pRX_StateArray[card];

		DeleteCriticalSection(&pRX_State->criticalSection) ;
	}
}

static int locks = 0;
static int unlocks = 0;
static int diffs;

PRX_API void CALL_CONV PRX_Lock(unsigned long card)
{
	PRX_State *pRX_State ;

	if (PRX_CheckState(card))
	{
		pRX_State = &pRX_StateArray[card];

		locks++;
		diffs = locks  - unlocks;
		if (diffs > 15)
		{
			printf("LOCK\n");
		}
		EnterCriticalSection(&pRX_State->criticalSection) ;
	}
}

PRX_API void CALL_CONV PRX_Unlock(unsigned long card)
{
	if (PRX_CheckState(card))
	{
		PRX_State *pRX_State = &pRX_StateArray[card];

		unlocks++;
		diffs = locks  - unlocks;
		LeaveCriticalSection(&pRX_State->criticalSection) ;
	}
}


// Create the critical section and inititalise the SDK's internal data structures
// PRX_Lock() is called as soon as the critical section is created to 
// prevent any other processes jumping in.  
// PRX_Unlock() is not called in this funciton.
// The caller must call PRX_Unlock() when initialisation
// has been completed

PRX_API NDTS_Boolean CALL_CONV PRX_StructuresInitialise(unsigned long card)
{
	PRX_State *pRX_State ;
//	long hpLookup[PRX_MAX_FRONTEND_FILTER_VALUE + 1] = {1,2,3,5,6,4,7,0} ;
//	long lpLookup[PRX_MAX_FRONTEND_FILTER_VALUE + 1] = {7,4,6,5,3,2,1,0} ;
	long hpLookup[PRX_MAX_FRONTEND_FILTER_VALUE + 1] = {0,7,4,6,5,3,2,1} ;
	long lpLookup[PRX_MAX_FRONTEND_FILTER_VALUE + 1] = {0,1,2,3,5,6,4,7} ;
	int i ;

	if (pRX_StateArray == NULL) return NDTS_BFalse; // can't initialise yet because statuc array isn't allocated
	
	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	PRX_CreateCriticalSection(card) ;

	PRX_Lock(card) ;

	if (pRX_State->isInitialised)
	{
		PRX_HandlePRXError(PRX_Initialise_AlreadyInitialised) ;
		return NDTS_BFalse ;
	}

	memset(&pRX_State->reg_shadow, 0, sizeof(PRX_Shadow));

	sprintf(pRX_State->diagnosticsFilename, "%s","PRX_diag.txt") ;

	for (i=0; i<= PRX_MAX_FRONTEND_FILTER_VALUE; i++)
	{
		pRX_State->frontEndHighPassLookup[i] = hpLookup[i] ;
		pRX_State->frontEndHighPassReverseLookup[hpLookup[i]] = i ;
		pRX_State->frontEndLowPassLookup[i] = lpLookup[i] ;
		pRX_State->frontEndLowPassReverseLookup[lpLookup[i]] = i ;
	}

	// All OK
	return NDTS_BTrue ;
}

PRX_API NDTS_Boolean CALL_CONV  PRX_PCIInitialise(unsigned long card)
{
	PRX_State *pRX_State;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (!NDTS_HAL_InitialiseCard(NDTS_SIDPRX, card))
		return NDTS_BFalse ;

	pRX_State->isPciInitialised = NDTS_BTrue ;	
	
	return NDTS_BTrue;
}


NDTS_Boolean PRX_CommitConditional(unsigned long card)
{
	PRX_State *pRX_State ;

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	if (pRX_State->isManualCommit == NDTS_BFalse)
	{
		return PRX_Commit(card);
	}
	else
	{
		return NDTS_BTrue;
	}
}

PRX_API void PRX_GetLogLevel(NDTS_LogLevel *pFile, NDTS_LogLevel *pApp, NDTS_LogLevel *pDebugger)
{
	NDTS_GetLogLevel(pFile, pApp, pDebugger);
}

PRX_API void PRX_SetLogLevel(NDTS_LogLevel file, NDTS_LogLevel app, NDTS_LogLevel debugger)
{
	NDTS_SetLogLevel(file, app, debugger);
}

// Limit the pulse width in the global registers if required to prevent a FET overload
NDTS_Boolean PRX_LimitPulseWidth(unsigned long card)
{
	PRX_State *pRX_State ;
	// From OverloadDetect.vhd
	int   pMaxOnTimeTable[8] = {163840, 40960, 16777, 9620, 3994, 2397, 1712, 1331} ;
	int ticksPeriod = 1048576;
	int volts ;
	int numSteps = 0;
	int ticksPerSequence = 0;
	int output = 0;
	int onTicks = 0;
	int step = 0;
	int safeOnTicks = 0;
	long long llSafeOnTicks = 0;
	int onCycles = 0;
	int pessimismTicks = 0;
	static enum NDTS_LogLevel debugLog = NDTS_LLNever;
	unsigned long pulseWidth[SEQUENCE_TABLE_BLOCKS];
	NDTS_Boolean oldIsOverload[SEQUENCE_TABLE_BLOCKS]; 

	if (!PRX_CheckState(card))
		return NDTS_BFalse ;

	pRX_State = &pRX_StateArray[card];

	volts = pRX_State->reg_shadow.globalRegs.pulserVoltage;

	if (!pRX_State->reg_shadow.globalRegs.highVoltageEnable)
		return NDTS_BTrue;

	//  Find the number of ticks in an entire "B Scan" cycle and work out the number of ticks that a channel can be on during this cycle
	do
	{	
		unsigned long extTrig = pRX_State->reg_shadow.globalRegs.externalTrigger;
		unsigned long ticksThisStep = (extTrig) ? pRX_State->masterRepetitionTime : pRX_State->reg_shadow.sequence[numSteps].repetitionDelay;
		ticksPerSequence += ticksThisStep * (pRX_State->reg_shadow.sequence[numSteps].stepSkip + 1);
		numSteps++;
	}
	while (pRX_State->reg_shadow.sequence[numSteps].sequenceFlag != 0ul);
	_ASSERTE(numSteps == (int)pRX_State->numberOfSequenceSteps);

	llSafeOnTicks = ((long long)pMaxOnTimeTable[volts >> 9] * (long long)ticksPerSequence) /   (long long)ticksPeriod;
	safeOnTicks = (int)llSafeOnTicks;

	NDTS_LogLine(debugLog,"Volts %d = %x.  MaxOnTable %d, ticksPerSequence %d, safeOnTicks %d",
		volts, volts, pMaxOnTimeTable[volts >> 9], ticksPerSequence, safeOnTicks);	



	// Check each FET channel against each sequence.   A FET channel can be on in more than one sequence.  More than one FET channel can be on in each step if parallel firing.
	// Find out the worst case pulse width for each sequence step.
	pRX_State->softwareFETOverloadOutputMask = 0;
	for (step=0; step<numSteps; step++)
	{
		oldIsOverload[step] = pRX_State->softwareFETOverloadIsThisSequenceLimited[step];
		pRX_State->softwareFETOverloadIsThisSequenceLimited[step] = NDTS_BFalse;
		pulseWidth[step] = pRX_State->reg_shadow.sequence[step].pulseWidth / 2;
	}

	for (output=0; output < PRX_CHANNELS; output++)
	{
		onTicks = 0;
		onCycles = 0;
		for (step=0; step<numSteps; step++)
		{

			PRX_SequenceTableBlock regs = pRX_State->reg_shadow.sequence[step];
			if (regs.transmitPulseEnable & (1 << output))
			{
				onTicks += regs.pulseWidth / 2 * (regs.pulseTrain + 1);  // pulseWidth is 5nS ticks
				onCycles++;
			}
		}
		// Firmware averages over ticksPeriod cycles.  Make a pessimistic assumption that FW will get an extra
		// pulse because it will start looking at the start of a sequence and get a pulse at the start of a following sequence just before it reeaches its count.
		if (onCycles > 0)
		{
			pessimismTicks = (int)((float)(onTicks / onCycles) * ((float)ticksPerSequence / (float)ticksPeriod));
//			NDTS_LogLine(NDTS_LLEmerg, "PessimismTicks %d, onTicks %d, onCycles %d, ticksPerSequence %d, ticksPeriod %d", 
//				pessimismTicks, onTicks, onCycles, ticksPerSequence, ticksPeriod);
			onTicks += pessimismTicks;
		}

		//NDTS_LogLine(debugLog, "Output %d, onTicks %d.", output, onTicks);	
		if (onTicks > safeOnTicks)
		{
			pRX_State->softwareFETOverloadOutputMask |= (1 << output);

			for (step=0; step<numSteps; step++)
			{
				PRX_SequenceTableBlock regs = pRX_State->reg_shadow.sequence[step];
				if (regs.transmitPulseEnable & (1 << output))
				{
					unsigned long newTicks = ((regs.pulseWidth/2) * safeOnTicks) / onTicks;
					pRX_State->softwareFETOverloadIsThisSequenceLimited[step] = NDTS_BTrue;
					if (pulseWidth[step] > newTicks)
					{
						NDTS_LogLine(debugLog, "Output %d(1 based), step %d, Pulse width %d(ticks), restricted to %d(ticks).", 
							output+1, step, pulseWidth[step], newTicks);	
						pulseWidth[step] = newTicks;
					}
				}
			}
		}
	}
	for (step=0; step<numSteps; step++)
	{
		if (pRX_State->softwareFETOverloadIsThisSequenceLimited[step])
		{
			// Apply the limiting to each step that requires it.  
			pRX_State->pLimitPulseWidthCallback(card, step, pulseWidth[step]);

		}
		else
		{
			if (oldIsOverload[step])
			{
				// If any step was limited and now isn't, restore its true width
				pRX_State->pLimitPulseWidthCallback(card, step, pulseWidth[step]);
			}
		}
	}

	return NDTS_BTrue;
}

NDTS_Boolean PRX_CheckState(unsigned long cardIndex)
{
	if (cardIndex >= prxs_found)
	{
		PRX_HandlePRXError(PRX_CheckState_InvalidCardIndex) ;
		return NDTS_BFalse ;
	}
 
	return NDTS_BTrue ;
}