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

#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>

#include "NDTSError.h"

#include "HardwareInitialise.h"
#include "RegisterBasedHardwareAccess.h"
#include "NDTS2100Internal.h"
#include "SDKUtilities.h"
#include "SnapshotProtected.h"

#define NDTS2100_FIRMWARE_REVISION 0xd02f

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

const int dataSamplingStatusRegisterAddress = 0x0030504c;

HardwareInitialise::HardwareInitialise(void)
{
}

HardwareInitialise::~HardwareInitialise(void)
{
}



const std::string HardwareInitialise::getExeDirectory()
{
      int nargs(0) ;
      LPWSTR *ppCmdLineArgs = CommandLineToArgvW(GetCommandLineW(), &nargs) ;
      const unsigned long bufferSize = 512 ;
      char pChars[bufferSize] ;
      WideCharToMultiByte(CP_UTF8, 0, ppCmdLineArgs[0], -1, pChars, bufferSize, NULL, NULL) ;
      std::string executableFileName(pChars) ;
      return executableFileName.substr(0, executableFileName.rfind('\\'));
}





 void HardwareInitialise::testSetAdc(int addr, int data)
 {
	Reg serialReg(0x00305004,0);
	// Set bit 25 serial reset
	serialReg.setValue(0x02000000);
	// Wait for serial write active to go to zero
	if (!serialReg.waitForBit(false, 31, 10))
	{
		NDTS_LogLine(NDTS_LLErr, "Timeout in testSetAdc");
		return ;
	}

	// Bottom 8 bits (0:7) are data
	int regValue = data;  

	// Bits 8:15 are set from our 8 bit address
	regValue |= (addr << 8);

	// Bit 23 is the data direction - 0 for write so leave at zero

	// Bit 24 is start transfer - set to 1
	regValue |= (1 << 24);

	// Bit 25 - serial reset is left as zero

	// Write the above bits
	serialReg.setValue(regValue);

	// Wait for serial write active to go to zero
	if (!serialReg.waitForBit(false, 31, 10))
	{
		NDTS_LogLine(NDTS_LLErr, "Timeout in testSetAdc");
		return ;
	}
 }

 bool  HardwareInitialise::initialiseFirmware()
 {
	 for (int card=0; card < globalState.numberOfCards; card++)
	 {
		 // Check the system FPGA invert register
		 Reg systemInvertTestRegister(0x00300000, card);
		 systemInvertTestRegister.setValue(0x12345678);
		 if (!systemInvertTestRegister.waitForBit(true, 31, 1000) || systemInvertTestRegister.getValue() != ~0x12345678)
		 {

			 NDTS_LogLine(NDTS_LLErr, "System FPGA invert check failed");
			 return false;
		 }

		 ///////////TEST/////////////
		 /* for (int i=0; i < 1000; i++)
		 {
		 int q=0;
		 for (int j=0; j < 1000; j++)
		 {
		 systemInvertTestRegister.setValue(0x12345678);
		 q = systemInvertTestRegister.getValue();0000000000
		 }
		 NDTS_LogLine(NDTS_LLErr, "step %d System FPGA invert check %x", i,  q);
		 } */
		 ///////////END TEST/////////////


		 // Open the file NDTS2100-D01.BIN that is in the same directory as the .exe where d 01 is the FPGA version number
		 std::ostringstream out ;
		 out << getExeDirectory() << "\\NDTS2100_" << hex << NDTS2100_FIRMWARE_REVISION << ".bin";
		 std::ifstream file(out.str().c_str(), std::ios::in | std::ios::binary) ;
		 if (!file)
		 {
			 NDTS_LogLine(NDTS_LLErr, "Firmware file %s not found", out.str().c_str());
			 return false;
		 }

		 // Initialise FPGA for configuration
		 Reg systemControlRegister(0x00300004, card);
		 systemControlRegister.setValue(0x1);

		 // Wait for config busy bit to go to zero
		 if (!systemControlRegister.waitForBit(false, 31, 10000))
		 {
			 NDTS_LogLine(NDTS_LLErr, "Time out after setting FPGA config bit in system control and waiting for the systemControlRegister busy bit\n");
			 return false;
		 }

		 // Write each word in the file to the config register
		 Reg fpgaConfigRegister(0x00300008, card);
		 int nextWord;
		 int i=0;
		 bool hasTimedOut = false;
		 int wordsWrittenOk = 0;
		 while (!file.eof())
		 {
			 file.read(reinterpret_cast<char *>(&nextWord), sizeof(nextWord)) ;
			 if (!hasTimedOut)
			 {
				 fpgaConfigRegister.setValue(nextWord);

				 // Wait for config busy bit to go to zero
				 if (!systemControlRegister.waitForBit(false, 31, 10000))
				 {
					 NDTS_LogLine(NDTS_LLErr, "Time out after writing %d words to the FPGA config register and waiting for the systemControlRegister busy bit\n",	i);
					 hasTimedOut = true;
					 wordsWrittenOk = i;
					 //return false;
				 }
			 }
			 i++;
		 }
		 file.close();
		 if (hasTimedOut)
		 {
			 NDTS_LogLine(NDTS_LLErr, "Time out after writing %d words - file has %d words\n",		wordsWrittenOk,		i);
			 hasTimedOut = true;
		 }


		 // Wait for successfully programmed bit to go to 1
		 if (!systemControlRegister.waitForBit(true, 30, 100))
		 {
			 NDTS_LogLine(NDTS_LLErr, "Time out after programming waiting for system config successfully programmed bit\n");
			 return false;
		 }

		 // Check the main FPGA version number register matches the file name
		 Reg versionNumberReg(0x00305ffc, card);
		 if (versionNumberReg.getValue() != NDTS2100_FIRMWARE_REVISION)
		 {
			 NDTS_LogLine(NDTS_LLErr, "FPGA version number %x does not match file version number %x",	versionNumberReg.getValue(),	NDTS2100_FIRMWARE_REVISION);
			 return false;
		 }

		 ///////////TEST/////////////
		 /*Reg ram(0x0, card);
		 int r=0;
		 r = ram.getValue();
		 NDTS_LogLine(NDTS_LLErr, "STEP %d RAM %d ", i,  r);
		 ram.setValue(0x12345);
		 r = ram.getValue();
		 NDTS_LogLine(NDTS_LLErr, "STEP %d RAM %d ", i,  r);
		 */

		 /*Reg testReg(0x00305000,0);
		 bool testFPGA = true;
		 int count = 0;
		 while(testFPGA)
		 {
		 for (int j=0; j < 1000000; j++)
		 {
		 int q=0;
		 for (int k =0; k<1000000; k++)
		 {
		 testReg.setValue(j);
		 q = testReg.getValue();
		 }

		 NDTS_LogLine(NDTS_LLErr, "count %d read back %x", count, q);
		 count++;
		 }
		 }*/

		 /*
		 ///////////END TEST/////////////


		 Reg inputSel(0x00301000, card);
		 inputSel.setValue(0x01000481);
		 Reg filterCtl(0x00301004, card);
		 filterCtl.setValue(0x00000808);

		 testSetAdc(0x0f, 0x02); // ain_config Bit 1 CML enable Bit 2 Analog input disable off 
		 testSetAdc(0x14, 0x01); // output_mode 2's complement  
		 testSetAdc(0x15, 0x04); // LVDS Fine adjust 2.75mA
		 testSetAdc(0x17, 0x02); // Output clock delay 0.2nS
		 testSetAdc(0x18, 0x00); // Input voltage range gain tweak (To be replaced by calibration algorithm)

		 Reg adcRaw(0x00305008, 0);
		 for (;;)
		 {
		 Sleep(1000);
		 NDTS_LogLine(NDTS_LLErr, "ADC raw is %x", adcRaw.getValue());
		 }
		 */

		 //NDTS_LogLine(NDTS_LLErr, "FPGA version number CHECK IS COMMENTED OUT %x >>>> %x", versionNumberReg.getValue(), NDTS2100_FIRMWARE_REVISION);
	 }

	 NDTS_LogLine(NDTS_LLNotice, "Running NDTS2100 SDK revision %s.  Firmware %x", SDKUtilities_GetSubversionRevisionString(), NDTS2100_FIRMWARE_REVISION) ;
	 return true;
 }

void  HardwareInitialise::prePropertiesInitialise()
{
	if (!NDTS2100_InitialiseInternal())
	{
		// If no hardware is found or the hardware init fails, carry on using default values
		_ASSERTE(!globalState.isRealHardwareFound);
	}

	if (globalState.isRealHardwareFound)
	{
		if (!initialiseFirmware())
		{
			NDTS2100_HardwareInitialiseFailed();
		}
	}
}

void  HardwareInitialise::postPropertiesInitialise()
{
	// Set registers that are not set by the default values above
	initialiseInternalValues();

	initialiseDataCollection();
	
}



NDTS2100_API NDTS_Boolean NDTS2100_ResetFirmware(unsigned long cardIndex)
{
	// We always do full A Scan capture in firmware, disable interrupts and enable sync pulse on sequence trigger 
	// If we are doing snapshot summary, store all the A in firmware and then choose which one to DMA in software.
	Reg ramControlReg(0x305028, cardIndex);
	ramControlReg.setValue(0x12);  
	return NDTS_BTrue;
}


// Initialise values that do not need to be changed by an application.
void HardwareInitialise::initialiseInternalValues()
{
	for (int card=0; card < globalState.numberOfCards; card++)
	{
		// Write fixed values to the RAM Control reg.
		NDTS2100_ResetFirmware(card);

		// Set the RAM bank size to 4MB - the initial version only uses the RAM mapped into a single window
		// See also NDTS2100_SetRAMWindow
		Reg ramBankSizeReg(0x0030501C, card);
		ramBankSizeReg.setValue(aScanMemoryBankSize + cScanMemoryBankSize);

		Reg cScanDataOffset(0x00305048, card);
		cScanDataOffset.setValue(aScanMemoryBankSize);
	}
}

// C functions that are passed as hooks to the C NDTSDataCollection.dll

NDTS2100_API NDTS_Boolean CALL_CONV NDTS2100_GetNumberOfBScansInSRAM(unsigned long cardIndex, unsigned long *numberOfScans)
{
	if (globalState.isRealHardwareFound)
	{
	    Reg bInRam(0x305040, cardIndex);
		*numberOfScans = bInRam.getValue() & 0x0000ffff;

		// Firmware not reliably updating this register.  Fix here!
		if (*numberOfScans > 100)
		{
			NDTS_LogLine(NDTS_LLErr, "Number of scans read as %d - clamping to 100", *numberOfScans);
			*numberOfScans = 100;
		}
	}
	else
	{
		*numberOfScans = 10;
	}
	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean CALL_CONV NDTS2100_SetRAMWindow(unsigned long cardIndex)
{
	Reg dataSamplingStatusReg(dataSamplingStatusRegisterAddress, cardIndex);

	int dss = dataSamplingStatusReg.getValue();

	// The total bank size is the size of the DDR RAM window mapped into the PCI address space
	// It must match the gap in the memory map
	static const int totalBankSize = aScanMemoryBankSize + cScanMemoryBankSize;
	_ASSERTE(totalBankSize == 0x00400000);

	// The RAM is configured so that each bank fits into the memory size window.
	Reg ramwindowReg(0x00305050, cardIndex);
	if (dss & 0x20)
	{
		ramwindowReg.setValue(totalBankSize);
	}
	else
	{
		ramwindowReg.setValue(0);
	}
	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean CALL_CONV NDTS2100_GetCScanBaseSRAMAddress(unsigned long cardIndex, unsigned long *baseAddress)
{
	*baseAddress = aScanBaseAddress + aScanMemoryBankSize;
	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean CALL_CONV NDTS2100_GetAScanBaseSRAMAddress(unsigned long cardIndex, unsigned long *baseAddress)
{
	*baseAddress = aScanBaseAddress;
	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean NDTS2100_GetSampleStorageMode(unsigned long cardIndex, NDTS_SampleSize *baseAddress)
{
	*baseAddress = sampleSize;
	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean NDTS2100_GetScanStorageOptions(unsigned long cardIndex, NDTS_ScanStorage *baseAddress)
{
	*baseAddress = scanStorage;
	return NDTS_BTrue;
}


NDTS2100_API NDTS_Boolean NDTS2100_CopyStateToRawScan(unsigned long cardIndex, NDTS_RawScan *pRawScan, NDTS_Boolean copyLiveNumberOfBScans)
{

	RawScan_RawScanProtected *pRawInternal = RawScan_ReturnRawScanInternal(pRawScan);
	unsigned long step, packedGate, unpackedGate ;
	DataCollectionValues *pDCV = &(globalState.dataCollectionValues[cardIndex]);

	// Preconditions.
	_ASSERTE(pRawScan) ;

	if ((int)cardIndex >= globalState.numberOfCards)
		return NDTS_BFalse;

	pRawScan->numberOfAScansInBScan = globalState.numberOfSteps[cardIndex];

	pRawScan->scanStorage = scanStorage;
	pRawScan->sampleSize = sampleSize;

	if (copyLiveNumberOfBScans)
	{
		if (!NDTS2100_GetNumberOfBScansInSRAM(cardIndex, &(pRawScan->numberOfBScans)))
			return NDTS_BFalse ;
		switch (pRawScan->scanStorage)
		{
		case NDTS_SSMultipleAScanNoCScan:
		case NDTS_SSMultipleAScanMultipleCScan:
			pRawScan->numberOfFullBScans = pRawScan->numberOfBScans;
			break;
		case NDTS_SSOneAScanMultipleCScan:
		case NDTS_SSOneBScanMultipleCScan:
			pRawScan->numberOfFullBScans = 1;
			break;
		default:
			_ASSERTE(0);
			pRawScan->numberOfFullBScans = 1;
		}
	}


	pRawScan->cardIndex		= cardIndex ;
	pRawScan->isSequence	= NDTS_BTrue ;
	//memcpy(pRawScan->oversampleCountThisStep, pRQState->oversampleCountThisStep, sizeof(pRQState->oversampleCountThisStep));

	memcpy(&(pRawScan->aScanSampleValuesThisStep), pDCV->aScanSampleValuesThisStep, sizeof(pDCV->aScanSampleValuesThisStep)) ;

	// The NDTS2100 allows a different samples divider per step, however the samples divider in the raw scan isn't used so just leave it at 1
	pRawScan->samplesDivider				= 1;

	// NDTS2100 doesn't do ping pong so set up 1:1
	for (int i=0; i<NDTS_MAXIMUM_NUMBER_OF_ELEMENTS; i++)
		pRawInternal->RPPS[i] = i;



	pRawInternal->gateBytesPerBScan			= pDCV->gateBytesPerBScan;
	pRawInternal->aScanBytesPerBScan		= pDCV->aScanBytesPerBScan;
	pRawInternal->numberToCheckEitherSide	= numberToCheckEitherSide ;
	pRawInternal->proportionToTriggerOff	= proportionToTriggerOff ;

	pRawInternal->isCRCCheckPresent			= isCRCCheckPresent;

	memcpy(&(pRawInternal->aScanOffsetThisStep), pDCV->aScanOffsetThisStep, sizeof(pDCV->aScanOffsetThisStep)) ;
	memcpy(&(pRawInternal->gateOffsetThisStep),  pDCV->gateOffsetThisStep, sizeof(pDCV->gateOffsetThisStep)) ;
	memcpy(&(pRawInternal->gateCountThisStep),   pDCV->gateCountThisStep, sizeof(pDCV->gateCountThisStep)) ;

	for (step=0; step < maxSteps; step++)
	{
		pRawInternal->oversampleCountThisStep[step] = 1;
		packedGate = 0 ;

		for (unpackedGate = 0 ; unpackedGate < maxGates; unpackedGate++)
		{
			if (pDCV->ppGateSettings[step][unpackedGate].isGateEnabled == NDTS_BTrue)
			{
				pRawScan->ppGateSettings[step][unpackedGate].isDistanceMetricPeak = pDCV->ppGateSettings[step][unpackedGate].isDistanceMetricPeak;
				pRawScan->ppGateSettings[step][unpackedGate].isGateEnabled = NDTS_BTrue;
				pRawScan->ppGateSettings[step][unpackedGate].snapshotSelection = snapshotSelection;
				pRawScan->ppGateSettings[step][unpackedGate].storeGateOnlyWhenTriggered = pDCV->ppGateSettings[step][unpackedGate].storeGateOnlyWhenTriggered;
				pRawScan->ppGateSettings[step][unpackedGate].triggerGate = pDCV->ppGateSettings[step][unpackedGate].triggerGate;
				packedGate++ ;
			}
			else
			{
				// Inactive gate - set default values
				pRawScan->ppGateSettings[step][unpackedGate].isGateEnabled = NDTS_BFalse;
				pRawScan->ppGateSettings[step][unpackedGate].isDistanceMetricPeak = NDTS_BTrue;
				pRawScan->ppGateSettings[step][unpackedGate].snapshotSelection = NDTS_PDSLatest;
				pRawScan->ppGateSettings[step][unpackedGate].storeGateOnlyWhenTriggered = NDTS_BTrue;
				pRawScan->ppGateSettings[step][unpackedGate].triggerGate = RAW_SCAN_GATE_TRIGGER_ABSOLUTE;
			}
		}

		pRawScan->numberOfGates[step] = packedGate ;
	}
	pRawInternal->numberOfEncodersSupportedByHardware = maxEncoders; 
	pRawInternal->dualEncoderWritesToEnc0and1 = NDTS_BFalse;
	pRawInternal->isLargeGateOutputData = NDTS_BTrue;

	return NDTS_BTrue;
}

NDTS2100_API NDTS_Boolean CALL_CONV NDTS2100_SetUpdateSampleParametersHook(unsigned long cardIndex, void (*pFN)(unsigned long cardIndex))
{
	globalState.pUpdateSampleParametersHook = pFN;
	return NDTS_BTrue;
}



NDTS2100_API unsigned long NDTS2100_ReturnOversampleCountThisStep(unsigned long cardIndex, unsigned long aScanIndex)
{
	cardIndex; aScanIndex;
	return 1;
}

NDTS2100_API unsigned long NDTS2100_ReturnAScanSampleValuesThisStep(unsigned long cardIndex, unsigned long aScanIndex)
{
	return globalState.dataCollectionValues[cardIndex].aScanSampleValuesThisStep[aScanIndex];
}
NDTS2100_API unsigned long NDTS2100_ReturnGateOffsetThisStep(unsigned long cardIndex, unsigned long aScanIndex)
{
	return globalState.dataCollectionValues[cardIndex].gateOffsetThisStep[aScanIndex];
}

NDTS2100_API unsigned long NDTS2100_ReturnGateBytesPerBScan(unsigned long cardIndex)
{
	return globalState.dataCollectionValues[cardIndex].gateBytesPerBScan;
}

NDTS2100_API unsigned long NDTS2100_ReturnAScanOffsetThisStep(unsigned long cardIndex, unsigned long aScanIndex)
{
	return globalState.dataCollectionValues[cardIndex].aScanOffsetThisStep[aScanIndex];
}

NDTS2100_API unsigned long NDTS2100_ReturnAScanBytesPerBScan(unsigned long cardIndex)
{
	return globalState.dataCollectionValues[cardIndex].aScanBytesPerBScan;
}

NDTS2100_API NDTS_Boolean NDTS2100_ReturnIsSignAndMag(unsigned long cardIndex)
{
	cardIndex; // Don't do sign and mag on 2100 as PCIe shouldn't need it.
	return NDTS_BFalse;
}
NDTS2100_API NDTS_SnapshotDataSelection NDTS2100_ReturnSnapshotSelectionForGate(unsigned long cardIndex, unsigned long step, unsigned long gate)
{
	cardIndex; step; gate;
	return snapshotSelection;
}


bool HardwareInitialise::initialiseDataCollection()
{
	DC_AssociatedSDK associatedSDK;

	associatedSDK.numberOfBoards = globalState.numberOfCards;
	associatedSDK.isRealHardwareFound = (globalState.isRealHardwareFound) ? NDTS_BTrue : NDTS_BFalse;
	associatedSDK.ignoreHeaderAndCRC = NDTS_BTrue;

	associatedSDK.addressOfDataSamplingStatusRegister = dataSamplingStatusRegisterAddress;
	associatedSDK.SID = NDTS_SID2100;  
	associatedSDK.maximumNumberOfGates = maxGates;
	associatedSDK.maximumNumberOfSamples = NDTS_MAXIMUM_NUMBER_OF_SAMPLES;
	associatedSDK.numberOfEncodersInHardware = 16; // There are up to 16 "Encoders" in the raw scan - some are spare at the moment
	associatedSDK.hasCRC = NDTS_BTrue;
	associatedSDK.fpLock = NDTS2100_Lock;
	associatedSDK.fpUnlock = NDTS2100_Unlock;
	associatedSDK.fpGetNumberOfBScansInSRAM = NDTS2100_GetNumberOfBScansInSRAM;
	associatedSDK.fpSetRAMWindow = NDTS2100_SetRAMWindow;
	associatedSDK.fpGetCScanBaseSRAMAddress = NDTS2100_GetCScanBaseSRAMAddress;
	associatedSDK.fpGetAScanBaseSRAMAddress = NDTS2100_GetAScanBaseSRAMAddress;
	associatedSDK.fpSetUpdateSampleParametersHook = NDTS2100_SetUpdateSampleParametersHook;
	associatedSDK.fpResetFirmware  = NDTS2100_ResetFirmware;  
	associatedSDK.fpDataFullRecovery = NDTS2100_ResetFirmware;  // Do the same reset on a data full - can point to another function if more needs to be done to get firmware to recover - see RQ SDK
	associatedSDK.fpGetSampleStorageMode = NDTS2100_GetSampleStorageMode;
	associatedSDK.fpGetScanStorageOptions = NDTS2100_GetScanStorageOptions;
	associatedSDK.fpCopyStateToRawScan = NDTS2100_CopyStateToRawScan;
	associatedSDK.fpReturnOversampleCountThisStep = NDTS2100_ReturnOversampleCountThisStep;
	associatedSDK.fpReturnAScanSampleValuesThisStep = NDTS2100_ReturnAScanSampleValuesThisStep;
	associatedSDK.fpReturnGateOffsetThisStep = NDTS2100_ReturnGateOffsetThisStep;
	associatedSDK.fpReturnGateBytesPerBScan = NDTS2100_ReturnGateBytesPerBScan;
	associatedSDK.fpReturnAScanOffsetThisStep = NDTS2100_ReturnAScanOffsetThisStep;
	associatedSDK.fpReturnAScanBytesPerBScan = NDTS2100_ReturnAScanBytesPerBScan;
	associatedSDK.fpReturnIsSignAndMag = NDTS2100_ReturnIsSignAndMag;
	associatedSDK.fpReturnSnapshotSelectionForGate = NDTS2100_ReturnSnapshotSelectionForGate;

	associatedSDK.maximumNumberOfGates = NDTS::NDTS2100::maxGates;
	if (!DC_SnapshotInitialise(&associatedSDK))
			return NDTS_BFalse;

	for (int card=0; card < globalState.numberOfCards; card++)
	{
		// Need to call updateSampleParameters before starting data collection
		// However, all the props have been sent by now so it won't be called automatically
		globalState.pUpdateSampleParametersHook(card);
		// NDTS2100 runs in full A Scan capture mode all the time at the moment as snapshot summary doesn't work for large gates
		DC_SetFullAScanCapture(card, NDTS_BTrue);
	}

	return NDTS_BTrue;
}
