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

#include "NDTSError.h"
#include "TCG.h"
#include "NDTS2100Internal.h"
#include "NDTS2100Errors.h"
#include "PropertiesInterface.h"
#include "PropertyInternal.h"
#include "PropertiesCollection.h"
#include "RegisterBasedHardwareAccess.h"
#include "NDTSCommonHal.h"
#include "SnapshotProtected.h"

using namespace NDTS::NDTS2100;
using namespace NDTS::SCO;


BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ 
	switch (ul_reason_for_call)
	{ 
	case DLL_PROCESS_ATTACH :
		NDTS2100_DLLInitialise();
		break ;
	case DLL_PROCESS_DETACH :
		NDTS2100_DLLUninitialise();
		break ;
	case DLL_THREAD_ATTACH :
		break ;
	case DLL_THREAD_DETACH :
		break ;
	}

	return TRUE ;
}

NDTS_Boolean CALL_CONV NDTS2100_DLLInitialise()
{
	globalState.isInitialised = false;
	// Register our GetCodeString function
	NDTS_RegisterGetCodeStringFunction(NDTS_ERROR_NDTS2100_SDK_INDEX, &NDTS2100_GetCodeString) ;
	return NDTS_BTrue ;
}

NDTS_Boolean CALL_CONV NDTS2100_DLLUninitialise()
{
	return NDTS_BTrue;
}

void NDTS2100_HardwareInitialiseFailed()
{
	globalState.isRealHardwareFound = false;
	globalState.numberOfCards = 3;
	NDTS2100_HandleNDTS2100Error(NDTS2100_NoHardwareFound);
	NDTS_LogLine(NDTS_LLErr, "No hardware found - defaulting to test mode with 3 cards");
}

NDTS_Boolean CALL_CONV NDTS2100_InitialiseInternal()
{
	NDTS_SetLogLevel(NDTS_LLEmerg, NDTS_LLDebug, NDTS_LLInfo);
	if (globalState.isInitialised)
	{
		// ToDo: Maybe set an error and return false once IW calls Init as the first thing.
		return NDTS_BTrue;
	}

	globalState.pUpdateSampleParametersHook = 0;
	for (int card=0; card < maxCards; card++)
	{
		for (int step=0; step < maxSteps; step++)
		{
			// Setting these values will force the data collection values to update when the default range / enables are applied from the SDK during initialisation
			globalState.dataCollectionValues[card].aScanSampleValuesThisStep[step] = -1;
			globalState.dataCollectionValues[card].ppGateSettings[step][0].isGateEnabled = NDTS_BTrue;
		}
		// Give these values that won't cause a div by zero error in the RAM size calcs before init is complete
		globalState.dataCollectionValues[card].aScanBytesPerBScan = 1;
		globalState.dataCollectionValues[card].gateBytesPerBScan = 1;
	}
	
	unsigned long nCards;
	NDTS_HAL_Initialise() ;
	// PCI RapiDAQ
	//	NDTS_HAL_InitialiseCardType(0x10b5,  0x5602, NDTS_SIDRapiDAQ,  &nCards) ;
	// 0x10b5 = PLX Vendor.  0x86e1, = PCIe bridge chip
	NDTS_HAL_InitialiseCardType(0x10b5, 0x86e1, NDTS_SID2100, &nCards) ;

	for (int card=0; card < maxCards; card++)
	{
		globalState.bScansInSnapshot[card] = 0;
	}
	globalState.lockEntryCount = 0;
	InitializeCriticalSection(&(globalState.criticalSection));
	if (nCards > 0)
	{
		globalState.numberOfCards = nCards;
		globalState.isRealHardwareFound = true;
		NDTS_LogLine(NDTS_LLInfo, "Found %d cards", globalState.numberOfCards);

		for (unsigned int cardIndex=0; cardIndex<nCards; cardIndex++)
		{
			if (!NDTS_HAL_InitialiseCard(NDTS_SID2100, cardIndex))
				return NDTS_BFalse ;
			if (!NDTS_HAL_OpenDMAChannel(NDTS_SID2100, cardIndex))
				return NDTS_BFalse ;
		}
	}
	else
	{
		NDTS2100_HardwareInitialiseFailed();
		return NDTS_BFalse;
	}

	return NDTS_BTrue;
}


	

// Intitialise the pci API to the cardIndex

FactoryInternal::FactoryInternal()
{
	_ASSERTE(!globalState.isInitialised);
	pPropertiesCollection = new SDKPropertiesCollection(this);
}

FactoryInternal::~FactoryInternal()
{

	if (globalState.isInitialised)
	{
		uninitialise();
	}
	delete pPropertiesCollection;
	DeleteCriticalSection(&(globalState.criticalSection));

	return;
}

Snapshot *FactoryInternal::getSnapshot()
{
	_ASSERTE(globalState.isInitialised);
	return &snapshot;
}
ErrorLog *FactoryInternal::getErrorLog()
{
	return &errorLog;
}
TCG *FactoryInternal::getTcg()
{
	_ASSERTE(globalState.isInitialised);
	return &tcg;
}


SnapshotInternal *FactoryInternal::getSnapshotInternal()
{
	return &snapshot;
}
TCGInternal *FactoryInternal::getTCGInternal()
{
	return &tcg;
}

NDTS::SCO::Property *FactoryInternal::getRootProperty()
{
	return dynamic_cast<NDTS::SCO::Property*>(pPropertiesCollection->getRoot());
}

bool FactoryInternal::initialise()
{
	if (!globalState.isInitialised )
	{
		hardwareInitialise.prePropertiesInitialise();
		pPropertiesCollection->initialise();
		hardwareInitialise.postPropertiesInitialise();
		globalState.isInitialised = true;
	}
	return globalState.isRealHardwareFound;;
}

void FactoryInternal::uninitialise()
{
	pPropertiesCollection->uninitialise();
}

int SnapshotInternal::returnNumberOfAScanSamples(int cardIndex, int sequenceIndex) 
{
	return DC_ReturnNumberOfAScanSamples(cardIndex, sequenceIndex);
}

bool SnapshotInternal::getAScan(int cardIndex, int sequenceIndex, int bScanIndex, short *pSamples, int numberOfBufferValues)
{
	if (!globalState.isInitialised)
	{
		NDTS2100_HandleNDTS2100Error(NDTS2100_NotInitialised) ;
		return false ;
	}

	StackBasedLock lock(cardIndex);
	if (!DC_GetFullAScanDataFromSnapshot(cardIndex, bScanIndex, sequenceIndex, pSamples, numberOfBufferValues))
		return false;

/*
		// Copy the A Scan from the first location in SRAM.
		for (int i=0; i<numberOfBufferValues*2; i+=4)
		{
			//NDTS_LogLine(NDTS_LLWarning, "NDTS_HAL_GetWord PCI Address %x local address %x", 0x40000000 + i, pSamples + i / 2);
			unsigned long value = 0;
		    NDTS_HAL_GetWord(NDTS_SID2100, card, 0x00400000 + i, &value);
			*(unsigned long*)(pSamples + i / 2) = value;
		}


		// Set bit  0 of 0x0030504c ?Data Available ?to 1.
		make call to DC_ if needed
		dataSamplingStatus.setValue(0x01);
		
	}
	else
	{
		// No real hardware - generate a simulated noise waveform

		//	for (int i=0; i < numberOfBufferValues; i++)
		//		pSamples[i] = i &0xfff - 250;

		for (int i=0; i < numberOfBufferValues; i++)
			pSamples[i] = rand() % 500 - 250;

			*/


	return true;
}

bool SnapshotInternal::getPackedGateData(int cardIndex, int sequenceIndex, int bScanIndex,  GateOutput *pGateOutputs, int numberOfBufferValues)
{
	StackBasedLock lock(cardIndex);
	    
	NDTS_GateOutputLarge pNDTSLargeGateOutputs[NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME];
	if (!DC_GetOneStepLargeGateDataFromSnapshot(cardIndex, bScanIndex, sequenceIndex, pNDTSLargeGateOutputs, NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME))
		return false;

	for(int i=0; i<numberOfBufferValues; i++)
	{
		if (i < NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME && pNDTSLargeGateOutputs[i].wasAmplitudeMeasured)
		{
			pGateOutputs[i].amplitudeMetric = pNDTSLargeGateOutputs[i].amplitudeMetric;
		}
		else
		{
			pGateOutputs[i].amplitudeMetric = 0;
		}
		if (i < NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME && pNDTSLargeGateOutputs[i].wasDistanceMeasured)
		{
			pGateOutputs[i].distanceMetric= pNDTSLargeGateOutputs[i].distanceMetric;
		}
		else
		{
			pGateOutputs[i].distanceMetric = 0;
		}
	}

	return true;
}

bool SnapshotInternal::getUnPackedGateData(int cardIndex, int sequenceIndex, int bScanIndex,  GateOutput *pGateOutputs, int numberOfBufferValues)
{
	StackBasedLock lock(cardIndex);
	    
	NDTS_GateOutputLarge pNDTSLargeGateOutputs[NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME];
	if (!DC_GetOneStepLargeUnPackedGateDataFromSnapshot(cardIndex, bScanIndex, sequenceIndex, pNDTSLargeGateOutputs, NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME))
		return false;

	for(int i=0; i<numberOfBufferValues; i++)
	{
		if (i < NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME && pNDTSLargeGateOutputs[i].wasAmplitudeMeasured)
		{
			pGateOutputs[i].amplitudeMetric = pNDTSLargeGateOutputs[i].amplitudeMetric;
		}
		else
		{
			pGateOutputs[i].amplitudeMetric = 0;
		}
		if (i < NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME && pNDTSLargeGateOutputs[i].wasDistanceMeasured)
		{
			pGateOutputs[i].distanceMetric= pNDTSLargeGateOutputs[i].distanceMetric;
		}
		else
		{
			pGateOutputs[i].distanceMetric = 0;
		}
	}

	return true;
}

bool SnapshotInternal::getEncoderValues(int cardIndex, int bScanIndex, int *pEncoderValues, int numberOfBufferValues)
{
	StackBasedLock lock(cardIndex);

	if (!DC_GetEncoderDataFromSnapshot(cardIndex, bScanIndex, (long*)pEncoderValues, numberOfBufferValues))
		return NDTS_BFalse;
/*
	//demo data
	static int count = 0;
	*pEncoderValue = count;
	count++;
    
	if (!globalState.isInitialised)
	{
		NDTS2100_HandleNDTS2100Error(NDTS2100_NotInitialised) ;
		return false ;
	}
	/*
	// Just pass this through to the RQ SDK with our raw-scan
	NDTS_Boolean isOK =  RQ_ExtractEncoderValue(bScanIndex, encoderIndex, &globalState.rawScan, (long*)pEncoderValue);
	return 	(isOK) ? true : false;
	*/
	return true;

}

void SnapshotInternal::takeSnapshot(int card)
{
	// Multicard - currently takeSnaphot is global not per card but left the card parameter so it is easy to change back
	// The for loops below would be removed if takeSnapshot was made per card
	_ASSERTE(card ==0); card;

	for (int cardIndex = 0; cardIndex < globalState.numberOfCards; cardIndex++)
		StackBasedLock lock(card);

	for (int cardIndex = 0; cardIndex < globalState.numberOfCards; cardIndex++)
	{
		// If software triggered, do a software trigger and wait for the data to be sampled - similar to the RS2A.
		if (globalState.dataCollectionValues[cardIndex].isSoftwareTriggered && globalState.isRealHardwareFound)
		{
			Reg forceSequenceTrigger(0x0030503C, cardIndex, 0, 0);
			forceSequenceTrigger.setValue(0x01);

			NDTS_Boolean isSomeDataSampled;
			// Wait for the firmware to acquire the scan
			do
			{
				if (!DC_GetIsSomeDataSampled(cardIndex, &isSomeDataSampled))
					return;
			}
			while (!isSomeDataSampled);
		}

		if (!DC_SwapSnapshotDataBank(cardIndex, &(globalState.bScansInSnapshot[cardIndex])))
		{
			NDTS_LogLine(NDTS_LLErr, "DC_SwapSnapshotDataBank returned false");
			_ASSERTE(0);
		}
	}
}

int SnapshotInternal::GetNumberOfBScansInSnapshot(int cardIndex)
{
	return 	globalState.bScansInSnapshot[cardIndex];
}


bool ErrorLogInternal::getErrorLogLine(int lineNumber, char *pMessageBuffer, int numberOfBytesInMessageBuffer)
{
	NDTS_Boolean isOK = NDTS_GetErrorLogLine(lineNumber, pMessageBuffer, numberOfBytesInMessageBuffer);
	return 	(isOK) ? true : false;
}

NDTS2100_API void CALL_CONV NDTS2100_Lock(unsigned long cardIndex)
{
	// MultiCard 
	// There is currently only one lock that stops all the cards - this will be good for ensuring things like 
	// takeSnapshot work well with synchornised cards
	// It will also ensure only one card is doing DMA at once, which may be a good or a bad thing.
	// It may be that it is better to allow cards to run in parallel, if so change the criticalSection to an array indexed by cardIndex
	cardIndex;

	EnterCriticalSection(&(globalState.criticalSection)) ;
	globalState.lockEntryCount++;
}

NDTS2100_API void CALL_CONV NDTS2100_Unlock(unsigned long cardIndex)
{
	cardIndex;
	--globalState.lockEntryCount;
	LeaveCriticalSection(&(globalState.criticalSection)) ;
}

StackBasedLock::StackBasedLock(unsigned long cardIndex)
{
	this->cardIndex = cardIndex;
	unlockedEarly = false;
	NDTS2100_Lock(cardIndex);
}
StackBasedLock::~StackBasedLock()
{
	if (!unlockedEarly)
		NDTS2100_Unlock(cardIndex);
}
void StackBasedLock::unlockEarly()
{
	if (!unlockedEarly)
		NDTS2100_Unlock(cardIndex);
	unlockedEarly = true;
}

