// Contains the SDK specific C++ functions and provides getRoot() and the root setting for the C++ interface

#include <crtdbg.h>

#include "NDTSRS2AInternal.h"
#include "PropertiesInterface.h"
#include "PropertyInternal.h"
#include "IntPropertyInternal.h"
#include "EnumPropertyInternal.h"
#include "SIShadowPropertyInternal.h"
#include "SIStoragePropertyInternal.h"
#include "SDKPropertiesCollection.h"
#include "PingPongTable.h"
#include "TCG.h"
#include "Collect.h"
#include "RQ.h"
#include "NDTSError.h"
#include "Gates.h"
// RS2APropertyNames contains string definitions that are requried by IW.
// We don't use the MFC / Windows style strings in the RS2A SDK so override the definitions.
#define LPCTSTR  char*
#define _T(x) x
#include "RS2APropertyNames.h"

using namespace NDTS::SCO;
using namespace NDTS::RS2A;

NDTSRS2A_GlobalState NDTS::RS2A::rs2aGlobalState;

static const int numberOfDigitalIOLines = 16;
static const int numberOfAnalogueInputLines = 16;
static const int numberOfAnalogueOutputLines = 16;

////////////////////////////////////////////////////////////////////////////////////////////
// Functions that wrap SDK calls that don't match the unsigned long, unsigned long signature
////////////////////////////////////////////////////////////////////////////////////////////
NDTS_Boolean CALL_CONV	RS2A_SetFractionalClicksPerBScan(unsigned long cardIndex, unsigned long fractionalClicks)
{
	// Applications may be written assuming a shift of 8.
	_ASSERTE(RQ_FRACTIONAL_BSCAN_CLICKS_UNITY_SHIFT == 8);
	return RQ_SetFractionalNumberOfEncoderClicksPerBScan(cardIndex, fractionalClicks, RQ_FRACTIONAL_BSCAN_CLICKS_UNITY_SHIFT);

}
NDTS_Boolean CALL_CONV	RS2A_SetBScanRepetitionRate(unsigned long cardIndex, unsigned long samplesPerBScan)
{
	// Convert to Hz, see comment for SetRepetitionRate.
	unsigned long repetitionRateHz = rs2aGlobalState.sampleFrequencyHz / samplesPerBScan;
	return RQ_SetBScanRepetitionRate(cardIndex, repetitionRateHz);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoNothing(unsigned long cardIndex, unsigned long value)
{
	cardIndex; value;
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetDoNothingEvent(unsigned long cardIndex)
{
	cardIndex; 
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetDoNormaliseGain(unsigned long cardIndex, unsigned long value)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setIsNormalisationOn((value == NDTS_BTrue));
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetDoInterfaceTriggeredTCG(unsigned long cardIndex, unsigned long value)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setIsInterfaceTriggered((value == NDTS_BTrue));
	return NDTS_BTrue;
}
NDTS_Boolean CALL_CONV	RS2A_SetDoTCGOrSimpleGain(unsigned long cardIndex, unsigned long value)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setIsSimpleGain(value == NDTS_BFalse);
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetSimpleGainValue(unsigned long cardIndex, unsigned long value)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setSimpleGain(value);
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetElementNormalisation(unsigned long elementIndex, unsigned long value)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setNormalisation(elementIndex, value);
	return NDTS_BTrue;
}



NDTS_Boolean CALL_CONV	RS2A_SetDoRectification(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetRectifierMode(cardIndex, (RQ_Rectification) value);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoInputVolts(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetAnalogueInputRange(cardIndex, (RQ_AnalogueInputRange) value);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoAnalogueInput(unsigned long cardIndex, unsigned long value)
{
	// Swap index 0 and index 1 because the RQ_AnalogueInput enum has Input 1 then Input 0
	RQ_AnalogueInput inputRange = (RQ_AnalogueInput) value;
	if (value == RQ_AIInput1)
		inputRange = RQ_AIInput0;
	else if (value == RQ_AIInput0)
		inputRange = RQ_AIInput1;

	return RQ_SetAnalogueInput(cardIndex, inputRange);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoInputCoupling(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetInputCoupling(cardIndex, (RQ_InputCoupling) value);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoInputImpedance(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetInputImpedance(cardIndex, (RQ_InputImpedance) value);
}

NDTS_Boolean CALL_CONV	RS2A_SetDIBoardState(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetDIBoardState(cardIndex, (NDTS_Boolean) value);
}

NDTS_Boolean CALL_CONV	RS2A_SetScanStorageOptions(unsigned long cardIndex, unsigned long value)
{
	switch(value)
	{
	case 0:
		return RQ_SetScanStorageOptions(cardIndex, NDTS_SSMultipleAScanMultipleCScan);
		break;
	case 1:
   		return RQ_SetScanStorageOptions(cardIndex, NDTS_SSOneBScanMultipleCScan);
		break;
	default:
		_ASSERTE(0);
		return NDTS_BFalse;
	}
}

NDTS_Boolean CALL_CONV	RS2A_SetDigitalOutput(unsigned long outputIndex, unsigned long isOn)
{
	/*
	int cardIndex = NDTS::RS2A::rs2aGlobalState.cardIndex;
	unsigned long value = 0;
	RQ_Lock(cardIndex);

	_ASSERTE(outputIndex < numberOfDigitalIOLines);

	NDTS_Boolean isOK = RQ_GetPeripheralIOBank0(cardIndex, &value);
	if (!isOK)
	{
		RQ_Unlock(cardIndex) ;
		return NDTS_BFalse ;
	}

	if (isOn)
		value |= 1 << outputIndex ;
	else
		value &= ~(1 << outputIndex) ;

	isOK = RQ_SetPeripheralIOBank0(cardIndex, value) ;
	if (!isOK)
	{
		RQ_Unlock(cardIndex) ;
		return NDTS_BFalse ;
	}

	RQ_Unlock(cardIndex) ;
	*/
	return NDTS_BTrue ;

}

NDTS_Boolean CALL_CONV	RS2A_GetDigitalInput(unsigned long inputIndex, unsigned long *pResult)
{
	int cardIndex = NDTS::RS2A::rs2aGlobalState.cardIndex;
	_ASSERTE(inputIndex < numberOfDigitalIOLines);
	/*
	RQ_Lock(cardIndex);

	unsigned long value = 0;
	NDTS_Boolean isOK = RQ_GetPeripheralIOBank0(cardIndex, &value) ;
	if (!isOK)
	{
		RQ_Unlock(cardIndex) ;
		return NDTS_BFalse ;
	}

	*pResult = (value & 1 << inputIndex) ? 1ul : 0ul;

	RQ_Unlock(cardIndex) ;
	*/
	return NDTS_BTrue ;
}
NDTS_Boolean CALL_CONV	RS2A_SetDataDirectionRegister(unsigned long index, unsigned long isOn)
{
	int cardIndex = NDTS::RS2A::rs2aGlobalState.cardIndex;
	unsigned long value = 0;
	/*
	RQ_Lock(cardIndex);

	_ASSERTE(index < numberOfDigitalIOLines);

	NDTS_Boolean isOK = RQ_GetPeripheralDDR0(cardIndex, &value);
	if (!isOK)
	{
		RQ_Unlock(cardIndex) ;
		return NDTS_BFalse ;
	}

	if (isOn)
		value |= 1 << index ;
	else
		value &= ~(1 << index) ;

	isOK = RQ_SetPeripheralDDR0(cardIndex, value) ;
	if (!isOK)
	{
		RQ_Unlock(cardIndex) ;
		return NDTS_BFalse ;
	}

	RQ_Unlock(cardIndex) ;
	*/
	return NDTS_BTrue ;

}
NDTS_Boolean CALL_CONV	RS2A_GetAnalogueInput(unsigned long inputIndex, unsigned long *pResult)
{
	*pResult = inputIndex* 10;
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_SetAnalogueOutput(unsigned long inputIndex, unsigned long value)
{
	return NDTS_BTrue;

}

NDTS_Boolean CALL_CONV	RS2A_GetEncoderValue(unsigned long encoderIndex, unsigned long *pResult)
{
	return RQ_GetEncoderOutput(NDTS::RS2A::rs2aGlobalState.cardIndex, encoderIndex, (long*)pResult) ;
}

NDTS_Boolean CALL_CONV	RS2A_ResetEncoder(unsigned long encoderIndex)
{
	return RQ_SetResetEncoderOutput(NDTS::RS2A::rs2aGlobalState.cardIndex, encoderIndex);
}

NDTS_Boolean CALL_CONV	RS2A_SetWaterPumpState(unsigned long cardIndex, unsigned long value)
{
	return RQ_SetWaterPumpState(cardIndex, (NDTS_Boolean) value);
}


NDTS_Boolean RS2A_TakeSnapshot(unsigned long cardIndex)
{

	NDTS_Boolean isOK = NDTS_BTrue, isSomeDataSampled = NDTS_BFalse;


	unsigned long totalBScans = 0;

	// Free up the old raw-scan
	if (!DC_FreeRawScanData(&rs2aGlobalState.rawScan, DC_FRSOAll))
		goto exit;

	// If software triggered - do the software trigger and wait for data.
	RQ_Trigger trigger;
	isOK = RQ_GetTriggerSelect(rs2aGlobalState.cardIndex, &trigger);
		if (!isOK)
			goto exit;
	if (trigger == RQ_TInternal)
	{
		// Do the software trigger
		isOK = RQ_SetForceBScanTrigger(rs2aGlobalState.cardIndex) ;
		if (!isOK)
			goto exit;

		NDTS_Boolean isSomeDataSampled;
		// Wait for the firmware to acquire the scan
		do
		{
			isOK = DC_GetIsSomeDataSampled(rs2aGlobalState.cardIndex, &isSomeDataSampled);
			if (!isOK)
				goto exit;
		}
		while (!isSomeDataSampled);
	}

	// isSomeDataAvailable is set as soon as the first A Scan arrives and does not mean the 
	// entire B Scan has been collected.
	NDTS_Boolean isSomeDataAvailable;
	isOK = DC_GetIsSomeDataSampled(rs2aGlobalState.cardIndex, &isSomeDataAvailable);
	if (!isOK)
		goto exit;

	if (!isSomeDataAvailable && !DC_HaveCollectedBScansInCurrentBank(rs2aGlobalState.cardIndex))
		goto exit;

	// Get the new raw-scan and store it in the global state
	if (!DC_GetRawScan(rs2aGlobalState.cardIndex, &rs2aGlobalState.rawScan))
		goto exit;

exit:
	return isOK;
}

NDTS_Boolean CALL_CONV	RS2A_GetBScansInSnapshot(unsigned long cardIndex, unsigned long *pNumberOfBScans)
{
	*pNumberOfBScans = rs2aGlobalState.rawScan.numberOfBScans;
	return NDTS_BTrue;
}


NDTS_Boolean CALL_CONV	RS2A_SetAScanRepetitionRate(unsigned long cardIndex, unsigned long samplesPerAScan)
{
	// All RS2A low level functions are in samples for consistency.
	// Some RQ functions are time based and rather than re-writing the RQ functions, RS2A converts the sample
	// parameter to time.
	// The sampling frequency property in the RS2A SDK refers to the sampling frequency
	// after adjustment by the drop samples.  The sampleFrequencyHz referst to the sampling frequency
	// in the RQ SDK and is a constant value.
	unsigned long repetitionRateHz = rs2aGlobalState.sampleFrequencyHz / samplesPerAScan;
	return RQ_SetRepetitionRate(cardIndex, repetitionRateHz);
}

// Intercept the number of samples so the TCG can be updated
NDTS_Boolean CALL_CONV RS2A_SetNumberOfSamples(unsigned long cardIndex, unsigned long samples) 
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setSamplesPerAScan(samples);
	return RQ_SetNumberOfSamples(cardIndex, samples);
}

NDTS_Boolean CALL_CONV	RS2A_SetDoSamplesDrop(unsigned long cardIndex, unsigned long samplesDrop)
{
	NDTS::RS2A::rs2aGlobalState.pTcg->setSamplesDropPerAScan(samplesDrop);
	return RQ_SetDropNumberOfSamples(cardIndex, samplesDrop);
}



NDTS_Boolean CALL_CONV	RS2A_GetNumberOfErrorLogLines(unsigned long cardIndex, unsigned long *pLines)
{
	*pLines = RS2A_ReturnNumberOfErrorLogLines();
	return NDTS_BTrue;
}

NDTS_Boolean CALL_CONV	RS2A_ClearErrorLog(unsigned long cardIndex)
{
	return NDTS_ClearErrorLog();
}

NDTS_Boolean CALL_CONV	RS2A_SetAuxAnalogueInRange(unsigned long channel, unsigned long range)
{
	return NDTS_BTrue;
}

// EnumStrings for properties that require a list of enumerated types.
const int numFalseTrueEnumStrings = 2;
char *falseTrueEnumStrings[numFalseTrueEnumStrings] = {"False",	"True"};
const int numScanTriggerModeEnumStrings = 3;
char *scanTriggerModeEnumStrings[numScanTriggerModeEnumStrings] = {"Simple Trig",	"Encoder Trig",	"Free Running"};
const int numApertureEnumStrings = 4;
char *apertureEnumStrings[numApertureEnumStrings] = {"8 Elements", "16 Elements", "24 Elements", "32 Elements"} ;
const int numInvertEnumStrings = 2;
char *invertEnumStrings[numInvertEnumStrings] = {"Not Inverted", "Inverted"} ;
const int numPingPongEnumStrings = 2;
char *pingPongEnumStrings[numPingPongEnumStrings] = {"PingPong Off", "PingPong On"} ;
const int numNormGainEnumStrings = 2;
char *normGainEnumStrings[numNormGainEnumStrings] = {"No Gain Norm.", "Gain Norm."} ;
const int numIntTrigTCGEnumStrings = 2;
char *intTrigTCGEnumStrings[numIntTrigTCGEnumStrings] = {"No Int Trig TCG", "Int Trig TCG"} ;
const int numTCGOrSimpleGainEnumStrings = 2;
char *tcgOrSimpleGainEnumStrings[numTCGOrSimpleGainEnumStrings] = {"Simple Gain", "TCG Curve"} ;
const int numRectEnumStrings = 4;
char *rectEnumStrings[numRectEnumStrings] = {"RF", "FW", "Pos", "Neg"} ;
const int numInputVoltsEnumStrings = 6;
char *inputVoltsEnumStrings[numInputVoltsEnumStrings] = {"100mV", "200mV", "500mV", "1V", "2V", "5V"};
const int numWaterOnOffEnumStrings = 2;
char *waterOnOffEnumStrings[numWaterOnOffEnumStrings] = {"Water Off", "Water On"} ;
const int numDIOnOffEnumStrings = 2;
char *dIOnOffEnumStrings[numDIOnOffEnumStrings] = {"Pulser Off", "Pulser On"} ;
const int numAnalogueInputEnumStrings = 6;
char *AnalogueInputEnumStrings[numAnalogueInputEnumStrings] = {"Analog Input 0", "Analog Input 1", "Input Calibration", "Input GND", "Input Aux0", "Input Aux1"};
const int numInputCouplingEnumStrings = 2;
char *InputCouplingEnumStrings[numInputCouplingEnumStrings] = {"DC", "AC"};
const int numInputImpedanceEnumStrings = 2;
char *InputImpedanceEnumStrings[numInputImpedanceEnumStrings] = {"1Meg", "500hm"};
const int numAuxAnalogueInRangeEnumStrings = 4;
char *auxAnalogueInRangeEnumStrings[numAuxAnalogueInRangeEnumStrings] = {"+-2V", "+-1V", "+-0.5V", "+-0.25V"};
const int numScanStorageOptionsStrings = 2;
char *scanStorageOptionsStrings[numScanStorageOptionsStrings] = {"Full A Scan", "First A Only"};
const int numTriggerGateNames = 9;
char *triggerGateNames[numTriggerGateNames] = {"Absolute", "Interface", "Gate 1", "Gate 2", "Gate 3", "Gate 4", "Gate 5", "Gate 6", "Gate 7"};
const int triggerGateEnumToValueMap[numTriggerGateNames] = {15, 0, 1, 2, 3, 4, 5, 6, 7};
const int numGatePolarityNames = 3;
char *gatePolarityNames[numGatePolarityNames] = {"Bipolar", "Positive", "Negative"};
const int numGateTrigModeNames = 2;
char *gateTrigModeNames[numGateTrigModeNames] = {"Level", "Edge"};
const int numGateDistanceMetricNames = 3;
char *gateDistanceMetricNames[numGateDistanceMetricNames] = {"Leading Flank", "Trailing Flank", "Peak"};
const int numGatePeakSelectNames = 16;
char *gatePeakSelectNames[numGatePeakSelectNames] = {"Largest Peak", "Last Peak", "Peak 0", "Peak 1", "Peak 2", "Peak 3", "Peak 4", "Peak 5", "Peak 6", "Peak 7", "Peak 8", "Peak 9", "Peak 10", "Peak 11", "Peak 12", "Peak 13"};
const int gatePeakSelectEnumToValueMap[numGatePeakSelectNames] = {15, 14, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

// The contstructor is called once when the .dll is loaded because of the singleton design pattern and static instance variable
SDKPropertiesCollection::SDKPropertiesCollection(TCGInternal *pTcg)
{
	this->pTcg = pTcg;
	rs2aGlobalState.cardIndex = 0;
	//rs2aGlobalState.rawScan 
	rs2aGlobalState.isInitialised = false;
	rs2aGlobalState.pTcg = pTcg;

	// It keeps things simple for both us and the app if the sample frequency is fixed.
	// If sampleFrequencyHz is changeable, something sensible must be done to properties such as the rep rate
	// which the application has set in Mhz.  Either produce a change in value for these properties
	// or alter the rep rate in the SDK to reflect the new sample rate (this may not always be possible)
	// Note that the sampleFrequencyHz here is the physical sample freqeuncy of the card whilst the SampleFrequency property
	// is scaled by the drop samples.
	rs2aGlobalState.sampleFrequencyHz = 100000000;

	// By experimentation, the current firmware (sfv = 0x122; pfv = 0x015;) is very unstable with 20 and seems to be generally stable with 50
	rs2aGlobalState.repSafetySamples = 50;

	int cardIndex = rs2aGlobalState.cardIndex;
	// Create the root group
	int numGroups = 10;
	Property **ppSubGroups = new Property *[numGroups];
	pRootGroup = new GroupPropertyInternal("Root", "The root of all the properties in this SDK", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	SCO::GroupPropertyInternal *pCurrentSubGroup = 0;

	//////////////////////////////////////////////////////////////////////////////////
	//  Special properties which we need to know the value of - created first
	//////////////////////////////////////////////////////////////////////////////////
	hookObject.pAScanRangeProp = new IntPropertyInternal(	"Range Samp Internal",				"Number Of Samples Per A Scan"		,			RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid,			1000,	2,	NDTS_MAXIMUM_NUMBER_OF_SAMPLES - 10, cardIndex, RS2A_SetNumberOfSamples);
	hookObject.pAScanRangeProp->isVisible = false;

	// Minimum is 5 clock ticks (100Mhz clock / 5). This is never reached due to the safe samples limit.  Maximum is 2^17 * 5 clock ticks but due to conversion to Hz and back, 653000 is a safe value.  Default of 200000 is enough to fit 1000 samples at 57 elements with plenty to spare
	hookObject.pAScanRepRateProp = new IntPropertyInternal(	"A Rep Rate Samp",			"A Scan Repetition Rate (samples)"	,			RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples |  RS2A::PF_NotCurrentlyValid,			200000,5,	653000,									cardIndex, RS2A_SetAScanRepetitionRate);
	hookObject.pAScanRepRateProp->isVisible = false;
	hookObject.pSampleFreqProp = new IntPropertyInternal(	"Sampling Frequency Int",	"Sampling frequency Integer value (Hz)",		RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_RdOnly| RS2A::PF_NotCurrentlyValid ,				rs2aGlobalState.sampleFrequencyHz,		0,	rs2aGlobalState.sampleFrequencyHz,		cardIndex, 0);
	hookObject.pSampleFreqProp->isVisible = false;
	hookObject.pAScanDelayProp = new IntPropertyInternal(	"A Scan Start Delay Samp",	"Samples delay from trigger to start sampling",	RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid,			2500,	0,	RQ_SRAM_MAXIMUM_START_DELAY- 1,			cardIndex, RQ_SetStartDelay);
	hookObject.pAScanDelayProp->isVisible = false;
	hookObject.pSamplesDropProp = new IntPropertyInternal(	"No. Samples Drop",			"Number of Samples Drop",						RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,								0,		0,	RQ_MAXIMUM_NUMBER_OF_SAMPLES_TO_DROP,	cardIndex, RS2A_SetDoSamplesDrop);

	// IsScanning can be "Not Scanning" or "Scanning".  This is only used to control the trigger at the moment.
	// This is taken care of at the RS2A level and the RQ SDK only knows about RQ_Trigger values.
	hookObject.pIsScanningProp = new EnumPropertyInternal(szIsScanning,				"Set triggering for scanning or setting up",	RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,	falseTrueEnumStrings, numFalseTrueEnumStrings, cardIndex, RS2A_SetDoNothing);
	// When scanning, Scanning can use Simple (same as idle), Encoder trigger or free running trigger modes
	hookObject.pScanTriggerModeProp=new EnumPropertyInternal(szScanningTriggerMode,	"Which trigger mode to use when scanning",		RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,	scanTriggerModeEnumStrings, numScanTriggerModeEnumStrings, cardIndex, RS2A_SetDoNothing);	
	// Simple trigger can be a software trigger, where the app calls RS2A_DoSoftwareTrigger, or external trigger where an external pulser input is used to trigger the card.
	hookObject.pIsExtTriggerProp = new EnumPropertyInternal(	"Is External Trigger",		"External trigger is not reliable with the current firmware",			RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,	falseTrueEnumStrings, numFalseTrueEnumStrings, cardIndex, RS2A_SetDoNothing);
	// RQ_NUMBER_OF_ENCODERS = 6, encoder is 0..5.  6 is the dual encoder made from encoders 0 and 1.
	hookObject.pEncoderToTriggerOffProp= new IntPropertyInternal(szTriggerEncoderNumber,		"Encoder number to trigger off",				RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid,	0,			0,			RQ_NUMBER_OF_ENCODERS,	cardIndex, 	RQ_SetEncoderSelect);


	hookObject.pNumberOfElementsProp=new IntPropertyInternal("Elements In Array",		"Number of physical array elements",			RS2A::PF_Int   | RS2A::PF_ForRS2A ,			64,				1,			NDTS_MAXIMUM_NUMBER_OF_ELEMENTS,					cardIndex,	RS2A_SetDoNothing);
	hookObject.pApertureSizeProp = new EnumPropertyInternal( "Aperture Size",			"Number of elements fired at once (array aperture)",RS2A::PF_Enum   | RS2A::PF_ForRS2A ,		apertureEnumStrings,	numApertureEnumStrings,		cardIndex,	RS2A_SetDoNothing);
	hookObject.pInvertArrayProp = new EnumPropertyInternal(	"Invert Array",				"Invert the array",								RS2A::PF_Enum   | RS2A::PF_ForRS2A ,			invertEnumStrings,		numInvertEnumStrings,		cardIndex,	RS2A_SetDoNothing);
	hookObject.pPingPongProp = new EnumPropertyInternal(		"Ping Pong",				"Optimise the array excitation pattern",		RS2A::PF_Enum   | RS2A::PF_ForRS2A ,		pingPongEnumStrings,	numPingPongEnumStrings,		cardIndex,	RS2A_SetDoNothing);
	hookObject.pElementMultiplierProp=new IntPropertyInternal("Pitch Multiplier",			"Element Pitch Multiplier - skips elements",RS2A::PF_Int   | RS2A::PF_ForRS2A ,			1,				1,			NDTS_MAXIMUM_NUMBER_OF_ELEMENTS,					cardIndex,	RS2A_SetDoNothing);
	hookObject.pAScansPerBScanProp = new IntPropertyInternal(szAscansPerBscan,		"Number of A Scans per B Scan, i.e. number of stripes in a C Scan",	RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_RdOnly ,	57,			1,	NDTS_MAXIMUM_NUMBER_OF_ELEMENTS, cardIndex,		0);

	hookObject.pTakeSnapshotProp = new EventPropertyInternal("Take Snapshot",	"Take a snapshot of the collected data", RS2A::PF_Event   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,	cardIndex, 	 RS2A_TakeSnapshot);
	// There is an inverse relationship between the frequency in the Int and the BASE SI unit of seconds, even though the reporting SI unit is Hz
	SIShadowPropertyInternal *pSampleFreqSIProp = new SIShadowPropertyInternal(szAscanSampleFrequency, "The effective sampling frequency, taking into account drop samples", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ |  RS2A::PF_RdOnly , SI::Hertz, SI::Mega, hookObject.pSampleFreqProp, 0, SIShadowPropertyInternal::InverseRelationship);
	// The sample frequency not taking into account drop samples.  Fixed at 100Mhz so set at default value and leave it there
	SIStoragePropertyInternal *pBaseSampleFreqSIProp = new SCO::SIStoragePropertyInternal("Base Sample Frequency",	"Base sampling frequency not taking into account drop samples, fixed at 100Mhz", RS2A::PF_Double   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_RdOnly, SI::Hertz, SI::Mega, 100.0, 100.0, 100.0);


	//////////////////////////////////////////////////////////////////////////////////
	// Snapshot functions - called by the app to trigger and take a snapshot
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Snapshot", "Triggering and taking a snapshot", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	pRootGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(new EventPropertyInternal("Software Trigger",	"*** NO LONGER IN USE.  Software trigger is now done automatically as part of takeSnapshot ****", RS2A::PF_Event   | RS2A::PF_NoLongerUsed | RS2A::PF_NotCurrentlyValid, cardIndex, RS2A_SetDoNothingEvent));
	pCurrentSubGroup->addProperty(hookObject.pTakeSnapshotProp);
	pCurrentSubGroup->addProperty(new IntPropertyInternal("B Scans in Snapshot",	"The number of B Scans in the currently collected snapshot", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid | RS2A::PF_RdOnly,		cardIndex, RS2A_GetBScansInSnapshot));

	//////////////////////////////////////////////////////////////////////////////////
	// Error log functions - used by the App to read the SDK error log
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Error Log", "Reading the SDK error log", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ  | RS2A::PF_NotCurrentlyValid);
	pRootGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(new IntPropertyInternal("Lines In Log",	"The number of lines currently in the error log", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid | RS2A::PF_RdOnly,		cardIndex, RS2A_GetNumberOfErrorLogLines));
	pCurrentSubGroup->addProperty(new EventPropertyInternal("Clear Log",	"Clear the entries from the log - call once the entries have been read", RS2A::PF_Event   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid,cardIndex, RS2A_ClearErrorLog));

	//////////////////////////////////////////////////////////////////////////////////
	// Acquisition - the main set of properties concerned with acquisition
	// This contains all the properties that are not special cases
	// And these properties appear in the first tab in IW.
	//////////////////////////////////////////////////////////////////////////////////
	SCO::GroupPropertyInternal *pAcquisitionGroup = new GroupPropertyInternal("Acquisition", "The main properties used for setting up a scan", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );

	//////////////////////////////////////////////////////////////////////////////////
	// Basic scan control - Rep rate, range, etc.
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Scan", "Settings to control the scan", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(hookObject.pAScanRangeProp);  // Frequency is an inverse relationship because it is Hz, i.e. seconds ^-1 and seconds is the SI base unit
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal(szAscanRange, "The range covered by an A Scan", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Seconds, SI::Micro, hookObject.pAScanRangeProp, pSampleFreqSIProp, SIShadowPropertyInternal::ProportionalRelationship));
	// The A Scan range and delay in samples need to be made available to the application.  We create new properties that reads the value, rather than exposing hookObject.pAScanRangeProp
	pCurrentSubGroup->addProperty(new IntPropertyInternal(	"Range Samp",				"Number Of Samples Per A Scan"		,			RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid| RS2A::PF_RdOnly, cardIndex, 	RQ_GetNumberOfSamples));
	pCurrentSubGroup->addProperty(new IntPropertyInternal(	"Delay Samp",				"Number Of Samples from trigger to start of A Scan capture"		,			RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid| RS2A::PF_RdOnly, cardIndex, RQ_GetStartDelay));


	pCurrentSubGroup->addProperty(hookObject.pAScanRepRateProp);
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("A Scan Repetition Rate", "The frequency at which A Scans are fired", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Hertz, SI::Kilo, hookObject.pAScanRepRateProp, pBaseSampleFreqSIProp, SIShadowPropertyInternal::ProportionalRelationship));
	pCurrentSubGroup->addProperty(hookObject.pAScanDelayProp);    // Delay has an integer unit of samples that is proportional to the time in seconds and so is Proprtional not inverse
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal(szAscanDelay, "The time between a trigger and the start of collection of A Scan data", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Seconds, SI::Micro, hookObject.pAScanDelayProp, pBaseSampleFreqSIProp, SIShadowPropertyInternal::ProportionalRelationship));
	pCurrentSubGroup->addProperty(hookObject.pSampleFreqProp);
	pCurrentSubGroup->addProperty(pSampleFreqSIProp);
	pCurrentSubGroup->addProperty(pBaseSampleFreqSIProp);
	pCurrentSubGroup->addProperty(hookObject.pSamplesDropProp);
	pCurrentSubGroup->addProperty(new PropertyInternal("Padding", "Dummy test for no longer used", RS2A::PF_NoLongerUsed  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal(szScanCaptureMode,	"Whether to capture full A Scan data",		RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , 	scanStorageOptionsStrings,		numScanStorageOptionsStrings,			cardIndex, RS2A_SetScanStorageOptions));
	hookObject.pIsStandAloneRQProp = (new EnumPropertyInternal(szIsStandAloneRQ,	"Set to True for single channel stand alone RapiDAQ card.  Leave as default false for RS2A",		RS2A::PF_Enum  | RS2A::PF_ForRapiDAQ , falseTrueEnumStrings,		numFalseTrueEnumStrings,			cardIndex, RS2A_SetDoNothing));
	pCurrentSubGroup->addProperty(hookObject.pIsStandAloneRQProp);

	pCurrentSubGroup->addProperty(new IntPropertyInternal("Gate Trailing Flank Holdoff",	"The gate trailing flank holdoff (0..256 samples)",		RS2A::PF_Int  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , 	0,							0,							1 << 7,									cardIndex, RQ_SetTrailingFlankHoldoff));
	pCurrentSubGroup->addProperty(new IntPropertyInternal("Maximum Number of Gates",	"The maximum number of gates",		RS2A::PF_Int | RS2A::PF_RdOnly,		NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME,	NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME,	NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME,	0,	0));

 
	//////////////////////////////////////////////////////////////////////////////////
	// Pulser control - currently just the pulse width
	//////////////////////////////////////////////////////////////////////////////////

	pCurrentSubGroup = new GroupPropertyInternal("Pulser",					"Settings to control the pulser",	RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	// See note in RQ.C : RQ_SetTransducerPulseWidth() for an explanation of the width and conversion to Mhz.
	IntPropertyInternal *pPulseWidth = new IntPropertyInternal("Pulse Width Samples",	"Transducer pulse width (samples)", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid,				20,				1,			RQ_MAXIMUM_GENERATOR_PULSE_LENGTH - 1, cardIndex, 	 RQ_SetTransducerPulseWidth);
	pPulseWidth->isVisible = false;
	pCurrentSubGroup->addProperty(pPulseWidth);
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("Pulse Width", "The width of the pulse that is fired", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Seconds, SI::Micro, pPulseWidth, pBaseSampleFreqSIProp, SIShadowPropertyInternal::ProportionalRelationship));

	//////////////////////////////////////////////////////////////////////////////////
	// Trigger control including whether we are scanning or idle
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Trigger",							"Settings to control the capture of scans",		RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(hookObject.pIsScanningProp);
	pCurrentSubGroup->addProperty(hookObject.pScanTriggerModeProp);
	pCurrentSubGroup->addProperty(hookObject.pIsExtTriggerProp);
	pCurrentSubGroup->addProperty(hookObject.pEncoderToTriggerOffProp);
	IntPropertyInternal *pEncoderClicksPerBScanProp = new IntPropertyInternal(szClicksPerBscan,	"Encoder clicks per B Scan when encoder trig.",	RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid,		50,				1,			65534,					cardIndex, RQ_SetNumberOfEncoderClicksPerBScan);
	pCurrentSubGroup->addProperty(pEncoderClicksPerBScanProp);
	// The SI properties for an encoder aren't used by IW and have never been tested so they are commented out.
	// IW forces the pitch in the scan direction so that it fits with an integer number of clicks per sample

	// IW doesn't use the fractional clicks per B Scan.  It is also not currenlty hooked up to the SI shadows for the encoder.
	// It would all need finishing off if an app needed it.
	// The fractional clicks are left exposed so that an app like WinRS can use it if it knows what it is doing.
	pCurrentSubGroup->addProperty(new IntPropertyInternal("Fractional clicks per B","Fractional clicks per B Scan n/256 (8 bit)",	RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid,	0,				0,			255,					cardIndex, RS2A_SetFractionalClicksPerBScan));
	//SIStoragePropertyInternal *pPitchInScanDirection = new SCO::SIStoragePropertyInternal("Pitch In Scan Direction", "Pitch in the direction of movement of the main encoder", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Metres, SI::Milli, 1.0, 0.01, 100);
//	pCurrentSubGroup->addProperty(pPitchInScanDirection);
	//hookObject.pDistancePerClickProp = new SIShadowPropertyInternal(szMillimetresPerClick, "The number of MilliMetres travelled in an encoder click", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Metres, SI::Milli, pEncoderClicksPerBScanProp, pPitchInScanDirection , SIShadowPropertyInternal::InverseRelationship);
	//pCurrentSubGroup->addProperty(hookObject.pDistancePerClickProp);
	// B Scan rep rate has 26 bits and is clock / 5 - set it in samples for RS2A.  It is set in Hz in the low level SDK and so can't be greater than the sample frequency (i.e. 1Hz)
	hookObject.pBScanRepRateSamplesProp = new IntPropertyInternal(	"B Scan Rep Rate (Samples)",			"B Scan repetition rate (samples)",				RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_IsInSamples| RS2A::PF_NotCurrentlyValid,			99999999,		10,			99999999,				cardIndex, RS2A_SetBScanRepetitionRate);
	hookObject.pBScanRepRateSamplesProp->isVisible = false;
	pCurrentSubGroup->addProperty(hookObject.pBScanRepRateSamplesProp);
	hookObject.pBScanRepRateSIProp = new SIShadowPropertyInternal("B Scan Repetition Rate", "The frequency at which B Scans are captured when free running", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Hertz, SI::Kilo, hookObject.pBScanRepRateSamplesProp, pBaseSampleFreqSIProp, SIShadowPropertyInternal::ProportionalRelationship);
	pCurrentSubGroup->addProperty(hookObject.pBScanRepRateSIProp);

	//////////////////////////////////////////////////////////////////////////////////
	// Array configuration  - The set value does nothing - the values must be OK or they would have failed a range test.  The array is updated in RS2A_ConfigureArray() that is called when any of the array properties change
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Array Configuration",					"Settings to control the array size and firing", RS2A::PF_Group   | RS2A::PF_ForRS2A );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	NDTS::SCO::SIStoragePropertyInternal *pPhysicalArrayPitch = new SCO::SIStoragePropertyInternal("Physical Array Pitch", "The distance in mm between each array element", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::Metres, SI::Milli, 0.8, 0.01, 100);
	pCurrentSubGroup->addProperty(pPhysicalArrayPitch);
	SIShadowPropertyInternal *pBeamWidth = new SIShadowPropertyInternal(szAscanSpacing, "The width of each beam that makes up the B Scan (Physical Array Pitch * Pitch Multiplier)", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_RdOnly, SI::Metres, SI::Milli, hookObject.pElementMultiplierProp, pPhysicalArrayPitch, SIShadowPropertyInternal::ProportionalRelationship);
	pCurrentSubGroup->addProperty(pBeamWidth);
	pCurrentSubGroup->addProperty(hookObject.pNumberOfElementsProp);
	pCurrentSubGroup->addProperty(hookObject.pApertureSizeProp);
	pCurrentSubGroup->addProperty(hookObject.pInvertArrayProp);
	pCurrentSubGroup->addProperty(hookObject.pPingPongProp);
	pCurrentSubGroup->addProperty(hookObject.pElementMultiplierProp);
	pCurrentSubGroup->addProperty(hookObject.pAScansPerBScanProp);
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("Scan width", "The width of the scan", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_RdOnly, SI::Metres, SI::Milli, hookObject.pAScansPerBScanProp, pBeamWidth, SIShadowPropertyInternal::ProportionalRelationship));
	//////////////////////////////////////////////////////////////////////////////////
	// Gain, including TCG
	//////////////////////////////////////////////////////////////////////////////////

	pCurrentSubGroup = new GroupPropertyInternal(		   "Gain",					"Settings to control the gain / TCG",			RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gain Normalise Enable",	"Apply a different gain to each element",		RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , 	normGainEnumStrings,		numNormGainEnumStrings,			cardIndex, RS2A_SetDoNormaliseGain));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal(szInterfaceTriggerTCG,	"Trigger the TCG from the interface gate",		RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,	intTrigTCGEnumStrings,		numIntTrigTCGEnumStrings,		cardIndex, RS2A_SetDoInterfaceTriggeredTCG));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal(szTCGOrSimpleGain,	"Use a TCG curve or use the simple gain value", RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ ,	tcgOrSimpleGainEnumStrings,	numTCGOrSimpleGainEnumStrings,	cardIndex, RS2A_SetDoTCGOrSimpleGain));
	IntPropertyInternal *pSimpleGain = new IntPropertyInternal( "Simple Gain (0 to 255)",	"Gain to apply accross whole A Scan 0..255",	RS2A::PF_Int  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid,		100,			0,			255,							cardIndex, RS2A_SetSimpleGainValue);
	pSimpleGain->isVisible = false;
	pCurrentSubGroup->addProperty(pSimpleGain);
	pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("Simple Gain", "Gain when no TCG curve is applied", RS2A::PF_Double  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , SI::dB, SI::NoPrefix, pSimpleGain, 0, SIShadowPropertyInternal::ProportionalRelationship, maxGainDecibels / 255));

	//////////////////////////////////////////////////////////////////////////////////
	// External Hardware - DI, water pump etc. that are attached to the RS2 Auto
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("External Hardware",		"Externally connected hardware e.g. water pump and DI unit", RS2A::PF_Group  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("DI unit relay",	"Turn the DI unit on / off",			RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , dIOnOffEnumStrings,	numDIOnOffEnumStrings,		cardIndex, RS2A_SetDIBoardState));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Water Pump",	"Turn the water mister pump on / off",	RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , waterOnOffEnumStrings,	numWaterOnOffEnumStrings,	cardIndex, RS2A_SetWaterPumpState));

	//////////////////////////////////////////////////////////////////////////////////
	// RapiDAQ Input Selection  - Should not be used for RS2A because values are set to suitable defaults for the DI unit
	//////////////////////////////////////////////////////////////////////////////////
	pCurrentSubGroup = new GroupPropertyInternal("Input",		"Input selection e.g. analogue",	RS2A::PF_Group   | RS2A::PF_ForRapiDAQ );
	pAcquisitionGroup->addProperty(pCurrentSubGroup);
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Input Range",		"Input Range",		RS2A::PF_Enum   | RS2A::PF_ForRapiDAQ ,	inputVoltsEnumStrings,		numInputVoltsEnumStrings  - 1, cardIndex, RS2A_SetDoInputVolts));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Analogue Waveform Input",	"The analogue input to the RapiDAQ on which the data is sampled",	RS2A::PF_Enum   | RS2A::PF_ForRapiDAQ ,	AnalogueInputEnumStrings,	numAnalogueInputEnumStrings  - 1, cardIndex, RS2A_SetDoAnalogueInput));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Input Coupling",	"Coupling Input",	RS2A::PF_Enum   | RS2A::PF_ForRapiDAQ ,	InputCouplingEnumStrings,	numInputCouplingEnumStrings, cardIndex, RS2A_SetDoInputCoupling));
	pCurrentSubGroup->addProperty(new EnumPropertyInternal("Input Impedance",	"Impedance Input",	RS2A::PF_Enum   | RS2A::PF_ForRapiDAQ ,	InputImpedanceEnumStrings,	numInputImpedanceEnumStrings, cardIndex, RS2A_SetDoInputImpedance));

	pRootGroup->addProperty(pAcquisitionGroup);


	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	//  Gate settings
	// The gate settings are an array. They are the first array to be used in the RS2A.
	// It is likely that IW will ignore these settings and use the LegacyGatesInterface.  That is why they are marked PF_NotCurrentlyValid
	//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	SCO::GroupPropertyInternal *pGateArrayGroup = new GroupPropertyInternal("Per Gate Settings Array", "Group indexed by Gate number with a sub-group for each Gate.  There is one of these groups for each step",			RS2A::PF_Group   | RS2A::PF_ForRapiDAQ | RS2A::PF_ForRS2A | PF_NotCurrentlyValid);
	pGateArrayGroup->isArray = true;
	pRootGroup->addProperty(pGateArrayGroup);
	for (int gate = 0; gate < NDTS_MAXIMUM_NUMBER_OF_GATES_COMPILE_TIME; gate++)
	{
		pCurrentSubGroup = new GroupPropertyInternal("Gate Settings Group", "Holds all the per Gate settings.  There is one of these groups for each Gate of each step of each card",	RS2A::PF_Group  | RS2A::PF_NotCurrentlyValid);
		pGateArrayGroup->addProperty(pCurrentSubGroup);

		GateObject *pGateObject = new GateObject(cardIndex, gate);
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Enable",					"The gate enable",																			RS2A::PF_Enum,					falseTrueEnumStrings,				numFalseTrueEnumStrings,					new GateElement(pGateObject, GF_isEnabled)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Trigger Gate",				"The gate which triggers this gate (or absolute)",											RS2A::PF_Enum,					triggerGateNames,					numTriggerGateNames,				(int*)(triggerGateEnumToValueMap),				new GateElement(pGateObject, GF_triggerGate)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Polarity",					"The polarity which triggers the gate",														RS2A::PF_Enum,					gatePolarityNames,					numGatePolarityNames,						new GateElement(pGateObject, GF_polarity)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Trigger Mode",				"The gate trigger mode",																	RS2A::PF_Enum,					gateTrigModeNames,					numGateTrigModeNames,						new GateElement(pGateObject, GF_triggerMode)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Distance Metric",			"The gate distance metric",																	RS2A::PF_Enum,					gateDistanceMetricNames,			numGateDistanceMetricNames,					new GateElement(pGateObject, GF_distanceMetric)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Amplitude Peak Select",	"The gate amplitude peak select",															RS2A::PF_Enum,					gatePeakSelectNames,				numGatePeakSelectNames,				(int*)(gatePeakSelectEnumToValueMap),			new GateElement(pGateObject, GF_amplitudePeakSelect)));
		pCurrentSubGroup->addProperty(new EnumPropertyInternal("Gate Distance Peak Select",		"The gate distance peak select",															RS2A::PF_Enum,					gatePeakSelectNames,				numGatePeakSelectNames,				(int*)(gatePeakSelectEnumToValueMap),			new GateElement(pGateObject, GF_distancePeakSelect)));
		
		IntPropertyInternal *pGateStart = new IntPropertyInternal("Gate Start Samples",			"The start time of the gate relative to the start of data capture (samples)",				RS2A::PF_Int | RS2A::PF_NotCurrentlyValid,				0,				0,			1 << 24,		new GateElement(pGateObject, GF_start));
		pCurrentSubGroup->addProperty(pGateStart);    // Delay has an integer unit of samples that is proportional to the time in seconds and so is Proprtional not inverse	
		pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("Gate Start",				"The start time of the gate relative to the start of data capture",							RS2A::PF_Double,				SI::Seconds,			SI::Micro,			pGateStart,			pBaseSampleFreqSIProp,			SIShadowPropertyInternal::ProportionalRelationship));
		IntPropertyInternal *pGateStop = new IntPropertyInternal("Gate Stop Samples",			"The Stop time of the gate relative to the Stop of data capture (samples)",					RS2A::PF_Int | RS2A::PF_NotCurrentlyValid,				0,				0,			1 << 24,		new GateElement(pGateObject, GF_stop));
		pCurrentSubGroup->addProperty(pGateStop);    // Delay has an integer unit of samples that is proportional to the time in seconds and so is Proprtional not inverse
		pCurrentSubGroup->addProperty(new SIShadowPropertyInternal("Gate Stop",					"The Stop time of the gate relative to the Stop of data capture",							RS2A::PF_Double,				SI::Seconds,			SI::Micro,			pGateStop,			pBaseSampleFreqSIProp,			SIShadowPropertyInternal::ProportionalRelationship));
		pCurrentSubGroup->addProperty(new IntPropertyInternal("Gate Threshold",					"The threshold at which the gate triggers (0..2047)",										RS2A::PF_Int,					0,						0,				(1 << 11) - 1,				new GateElement(pGateObject, GF_threshold)));
 
	}
    
	//////////////////////////////////////////////////////////////////////////////////
	// Array normalisation - Goes in its own tab (high level group)
	//////////////////////////////////////////////////////////////////////////////////

	// Create an array and SI shadow for the gain adjustment for each beam, i.e. the normalisation values
	pCurrentSubGroup = new GroupPropertyInternal("Beam Gain",		"The gain offset for a fired beam",	RS2A::PF_Group   | RS2A::PF_ForRapiDAQ | RS2A::PF_ForRS2A );

	GroupPropertyInternal *pGainOffsetForElementInt = new GroupPropertyInternal("Gain Offset For Beam (Normalisation) (Int)", "The gain offset for a fired beam, index is the scan line", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	const int maxBeamAdjustChars = 50;
	static char intBeamAdjustNames[NDTS_MAXIMUM_NUMBER_OF_ELEMENTS][maxBeamAdjustChars];
	IntPropertyInternal *pGainAdjust[NDTS_MAXIMUM_NUMBER_OF_ELEMENTS];
	for (int i=0; i < NDTS_MAXIMUM_NUMBER_OF_ELEMENTS; i++)
	{
		sprintf(intBeamAdjustNames[i], "Beam Gain Adjust (Int) %d", i+1);
		pGainAdjust[i] = new IntPropertyInternal(intBeamAdjustNames[i],	"Gain adjustment for an individual element", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_NotCurrentlyValid ,		0, -255, 255, i,  RS2A_SetElementNormalisation);
		pGainAdjust[i]->isVisible = false;
		pGainOffsetForElementInt->addProperty(pGainAdjust[i]);
	}
	pCurrentSubGroup->addProperty(pGainOffsetForElementInt);

	GroupPropertyInternal *pGainOffsetForElement = new GroupPropertyInternal(szBeamNormalisationGroup, "The gain offset for an array element, index is the scan line", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ);
	static char siBeamAdjustNames[NDTS_MAXIMUM_NUMBER_OF_ELEMENTS][maxBeamAdjustChars];
	for (int i=0; i < NDTS_MAXIMUM_NUMBER_OF_ELEMENTS; i++)
	{
		sprintf(siBeamAdjustNames[i], "Beam Gain %d", i+1);
		pGainOffsetForElement->addProperty(new SIShadowPropertyInternal(siBeamAdjustNames[i], "Gain adjustment for an individual element", RS2A::PF_Double  | RS2A::PF_ForRS2A , SI::dB, SI::NoPrefix, pGainAdjust[i], 0, SIShadowPropertyInternal::ProportionalRelationship, maxGainDecibels / 255));
	}
	pCurrentSubGroup->addProperty(pGainOffsetForElement);
	pRootGroup->addProperty(pCurrentSubGroup);



	//////////////////////////////////////////////////////////////////////////////////
	// Input and Output including live encoder reading
	//////////////////////////////////////////////////////////////////////////////////

	// Create an array and SI shadow for the gain adjustment for each beam, i.e. the normalisation values
	pCurrentSubGroup = new GroupPropertyInternal("Input and Output",		"Digital and Analogue IO and encoders",	RS2A::PF_Group  | RS2A::PF_ForRS2A  | RS2A::PF_ForRapiDAQ );

	GroupPropertyInternal *pDIOLines = new GroupPropertyInternal("Digital I/O", "Digital Input / Outputs ", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	const int maxIOChars = 50;
	static char dioNames[numberOfDigitalIOLines][maxIOChars];
	for (int i=0; i < numberOfDigitalIOLines; i++)
	{
		sprintf(dioNames[i], "Digital I/O Line %d", i+1);
		pDIOLines->addProperty(new IntPropertyInternal(dioNames[i],	"Digital I/O Line - read if an input or write if an output", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,		0, 0, 1, i,   RS2A_SetDigitalOutput, RS2A_GetDigitalInput));
	}
	pCurrentSubGroup->addProperty(pDIOLines);

	GroupPropertyInternal *pDDR = new GroupPropertyInternal("Digital I/O Data Direction Register", "Digital I/O Data Direction Register set to 1 for output 0 for input", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char ddrNames[numberOfDigitalIOLines][maxIOChars];
	for (int i=0; i < numberOfDigitalIOLines; i++)
	{
		sprintf(ddrNames[i], "Data Direction For Line %d", i+1);
		pDDR->addProperty(new IntPropertyInternal(ddrNames[i],	"Digital I/O Data Direction Register 1 for output 0 for input", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,		0, 0, 1, i,   RS2A_SetDataDirectionRegister));
	}
	pCurrentSubGroup->addProperty(pDDR);

	GroupPropertyInternal *pAnalogueInputs = new GroupPropertyInternal("Analogue Inputs", "Auxilliary Analogue inputs not used for sampling a waveform.  Range of (0..65535)", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char pAnalogueInputNames[numberOfAnalogueInputLines][maxIOChars];
	for (int i=0; i < numberOfAnalogueInputLines; i++)
	{
		sprintf(pAnalogueInputNames[i], "Analogue Input For Line %d", i+1);
		pAnalogueInputs->addProperty(new IntPropertyInternal(pAnalogueInputNames[i],	"", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,	i,   RS2A_GetAnalogueInput));
	}
	pCurrentSubGroup->addProperty(pAnalogueInputs);

	GroupPropertyInternal *pAIRange = new GroupPropertyInternal("Aux Ana In Volt Range",	"The voltage range of the auxilliary analogue inputs",  RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char pAIRangeChars[numberOfAnalogueInputLines][maxIOChars];
	for (int i=0; i < numberOfAnalogueInputLines; i++)
	{
		sprintf(pAIRangeChars[i], "Aux Ana In Volt Range For Line %d", i+1);
		pAIRange->addProperty(new EnumPropertyInternal(pAIRangeChars[i],	"The voltage range of the auxilliary analogue inputs",	RS2A::PF_Enum  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ , auxAnalogueInRangeEnumStrings,	numAuxAnalogueInRangeEnumStrings,	i, RS2A_SetAuxAnalogueInRange));
	}
	pCurrentSubGroup->addProperty(pAIRange);

	GroupPropertyInternal *pAnalogueOutputs = new GroupPropertyInternal("Analogue Outputs", "Analogue Outputs (0..4095, 1 bit / mV)", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char pAnalogueOutputNames[numberOfAnalogueOutputLines][maxIOChars];
	for (int i=0; i < numberOfAnalogueOutputLines; i++)
	{
		sprintf(pAnalogueOutputNames[i], "Analogue Output For Line %d", i+1);
		pAnalogueOutputs->addProperty(new IntPropertyInternal(pAnalogueOutputNames[i],	"", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,	128, 0, 255, i,   RS2A_SetAnalogueOutput));
	}
	pCurrentSubGroup->addProperty(pAnalogueOutputs);
	
	GroupPropertyInternal *pEncoderValues = new GroupPropertyInternal("Encoder Values", "Encoder Values In Clicks", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char pEncoderValueNames[RQ_NUMBER_OF_ENCODERS][maxIOChars];
	for (int i=0; i < RQ_NUMBER_OF_ENCODERS; i++)
	{
		sprintf(pEncoderValueNames[i], "Encoder Value %d", i+1);
		pEncoderValues->addProperty(new IntPropertyInternal(pEncoderValueNames[i],	"Encoder Value (Clicks)", RS2A::PF_Int   | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,	i,   RS2A_GetEncoderValue, true));
	}
	pCurrentSubGroup->addProperty(pEncoderValues);

	GroupPropertyInternal *pEncoderResets = new GroupPropertyInternal("Encoder Resets", "Encoder Resets", RS2A::PF_Group  | RS2A::PF_Array | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid);
	static char pEncoderResetNames[RQ_NUMBER_OF_ENCODERS][maxIOChars];
	for (int i=0; i < RQ_NUMBER_OF_ENCODERS; i++)
	{
		sprintf(pEncoderResetNames[i], "Encoder Reset %d", i+1);
		pEncoderResets->addProperty(new EventPropertyInternal(pEncoderResetNames[i],	"Encoder Reset ", RS2A::PF_Event  | RS2A::PF_ForRS2A | RS2A::PF_ForRapiDAQ | RS2A::PF_NotCurrentlyValid ,	i,   RS2A_ResetEncoder));
	}
	pCurrentSubGroup->addProperty(pEncoderResets);

	pRootGroup->addProperty(pCurrentSubGroup);

	pRootGroup->setGroupHooks(dynamic_cast<GroupHooks*>(&hookObject));
	hookObject.setTcg(pTcg);
}

SDKPropertiesCollection::~SDKPropertiesCollection(void)
{
	delete pRootGroup;
}

// Return the root setting - all the application needs
GroupProperty *SDKPropertiesCollection::getRoot()
{
	return dynamic_cast<NDTS::SCO::GroupProperty*>(pRootGroup);
}

bool  SDKPropertiesCollection::initialise()
{
	if (!RS2A_InitialiseInternal())
		return false;
	if (!initialiseInternalValues())
		return false;
	pRootGroup->initComplete();

	return true;
}

void  SDKPropertiesCollection::uninitialise()
{
	if (!rs2aGlobalState.isInitialised)
		return;
	rs2aGlobalState.isInitialised = false;
	// Turn off the DI unit relay on shutdown 	
	NDTS_Boolean isOk = RQ_SetDIBoardState(rs2aGlobalState.cardIndex, NDTS_BTrue) ;
	DC_FreeRawScanData(&rs2aGlobalState.rawScan, DC_FRSOAll) ;
	DC_CollectUninitialise();
	RQ_Uninitialise(rs2aGlobalState.cardIndex);
}


// Initialise values that do not need to be changed by an application.
bool SDKPropertiesCollection::initialiseInternalValues()
{
	NDTS_Boolean isOK;

	hookObject.pTcg->initialise();

	// The sampling frequency property in the RS2A SDK refers to the sampling frequency
	// after adjustment by the drop samples.  The sampleFrequencyHz referst to the sampling frequency
	// in the RQ SDK and is a constant value.
	isOK = RQ_SetSamplingFrequency(rs2aGlobalState.cardIndex, rs2aGlobalState.sampleFrequencyHz) ;
	if (!isOK)
		return NDTS_BFalse;

	isOK = RQ_SetIsBScanModeEnabled(rs2aGlobalState.cardIndex, NDTS_BTrue) ;
	if (!isOK)
		return NDTS_BFalse;

	isOK = RQ_SetDIInterfaceBitsLow(rs2aGlobalState.cardIndex) ;
	if (!isOK)
		return NDTS_BFalse;

	isOK = RS2A_SetDoRectification(rs2aGlobalState.cardIndex, RQ_RBipolar) ;
	if (!isOK)
		return NDTS_BFalse;

	isOK = RS2A_SetDIBoardState(rs2aGlobalState.cardIndex, NDTS_BTrue) ;
	if (!isOK)
		return NDTS_BFalse;

	isOK = RS2A_SetWaterPumpState(rs2aGlobalState.cardIndex, NDTS_BFalse) ;
	if (!isOK)
		return NDTS_BFalse;

	// Always generate a B Scan sync pulse - shouldn't be any reason not to
	isOK = RQ_SetSyncPulseMode(rs2aGlobalState.cardIndex, NDTS_BTrue) ;
	if (!isOK)
		return NDTS_BFalse;

	/*	isOK = RQ_SetTestPatternMode(rs2aGlobalState.cardIndex, NDTS_BTrue) ;
	if (!isOK)
	return NDTS_BFalse;
	*/
	return NDTS_BTrue;
}
