#include <crtdbg.h>

#include "Collect.h"
#include "NDTSRS2A.h"
#include "NDTSRS2AInternal.h"
#include "NDTSRS2AErrors.h"
#include "PropertiesInterface.h"
#include "PropertyInternal.h"
#include "IntPropertyInternal.h"
#include "EnumPropertyInternal.h"
#include "SDKPropertiesCollection.h"
#include "PingPongTable.h"
#include "TCG.h"
#include "Rq.h"

#include "SDKChangeHook.h"


using namespace NDTS::SCO;
using namespace NDTS::RS2A;
static unsigned long boardIndex = 0;  // RS2A currently designed to work with only one board

SDKChangeHook::SDKChangeHook(void)
{
	pTcg = 0;
	postSetUpdateDisabled = false;
}

SDKChangeHook::~SDKChangeHook(void)
{
}

//////////////////////////////////////////////////////////////
// Hooks that are called by the SdkCommonObjects (SCO)
//////////////////////////////////////////////////////////////

bool SDKChangeHook::isOkToSet(PropertyInternal* pProperty)
{
	if (pProperty != pIsScanningProp && pProperty != pTakeSnapshotProp &&  pIsScanningProp->getValue() != 0)
	{
		RS2A_HandleRS2AError(RS2A_PropertySetWhenScanning);
		return false;
	}
	return true;
}

void SDKChangeHook::postSetUpdate(IntPropertyInternal *pProp)
{
	if (postSetUpdateDisabled)
		return;
	// Special cases for certain properties are handled here
	// For example updating the max of other properties
	// Or handling a change in shape of the array
	// The property must be OK by the time we get this far
	if (pProp == pAScanRangeProp || pProp == pAScanRepRateProp || pProp == pAScanDelayProp || pProp == pSamplesDropProp)
		updateRangeDelayRepMax();

	// Changes in the shape of the array are all handled by RS2A_ConfigureArray.
	// Each of these properties set functions point to the RS2A_SetDoNothing function
	if (pProp == pNumberOfElementsProp || pProp == pApertureSizeProp  || pProp == pInvertArrayProp || 
		pProp == pPingPongProp || pProp == pElementMultiplierProp)
	{
		configureArray();
		updateRangeDelayRepMax();
	}
	if (pProp == pIsScanningProp || pProp == pScanTriggerModeProp || pProp == pIsExtTriggerProp)
	{
		configureTriggering();
	}
	if (pProp == pIsScanningProp || pProp == pScanTriggerModeProp || pProp == pIsExtTriggerProp)
	{
		configureTriggering();
	}
	if (pProp == pIsStandAloneRQProp && pProp->getValue() != 0)
	{
		// Apply special array settings to force single channel if stand alone rapidaq
		configureStandAloneRQ();
	}
}


void SDKChangeHook::postEventUpdate(EventPropertyInternal *pProp)
{
}



// The A Scan delay + range + safety samples must be less than the A Scan rep rate.  If any of the 
// three properties change, this function is called to adjust the max values.
// Returns true if any of the max values have been changed.
// Some of this logic should be moved into classes in the SCO.dll if it is useful for other SDKs
void SDKChangeHook::updateRangeDelayRepMax()
{
	// Initialising - don't update values until all properties are valid.
	if (pAScanRangeProp->getUncommittedValue() == -1 || pAScanDelayProp->getUncommittedValue() == -1 || 
		pAScanRepRateProp->getUncommittedValue() == -1 || pAScansPerBScanProp->getUncommittedValue() == -1 || 
		pBScanRepRateSamplesProp->getUncommittedValue() == -1 || pSampleFreqProp->getUncommittedValue() == -1 || 
		pSamplesDropProp->getUncommittedValue() == -1)
		return;

	// Check the A Scan delay + range is less than rep rate, allowing some safety margin
	int activeSamples = pAScanRangeProp->getUncommittedValue() + pAScanDelayProp->getUncommittedValue() + rs2aGlobalState.repSafetySamples;
	int spareSamples = pAScanRepRateProp->getUncommittedValue() - activeSamples;

	// A Scan can't be greater than 16 bits with a little safety margin
	int minAScanRangeMax = NDTS_MAXIMUM_NUMBER_OF_SAMPLES - 10;

	// Make sure the A Scan will also fit inside the current rep rate
	if (spareSamples + pAScanRangeProp->getUncommittedValue() < minAScanRangeMax)
		minAScanRangeMax = spareSamples + pAScanRangeProp->getUncommittedValue();

	// The A Scan must also fit into available memory
	int otherMems = pAScansPerBScanProp->getUncommittedValue() * 32 + 4 + RQ_NUMBER_OF_ENCODERS * 4;
	if ((RQ_SRAM_BANK_SIZE - otherMems) / ( pAScansPerBScanProp->getUncommittedValue()  * 2) < minAScanRangeMax)
		minAScanRangeMax = (RQ_SRAM_BANK_SIZE - otherMems) / (pAScansPerBScanProp->getUncommittedValue()  * 2);

	pAScanRangeProp->updateMax(minAScanRangeMax);		

	pAScanDelayProp->updateMax(spareSamples + pAScanDelayProp->getUncommittedValue());
	pAScanRepRateProp->updateMin(activeSamples);
	// B Scan rep rate is set in RQ by converting to Hz and back with possibility of a rounding error and then checked against A per B + 1 so add spareSamples and + to a per B as safety margin
	pBScanRepRateSamplesProp->updateMin((activeSamples + spareSamples) * (pAScansPerBScanProp->getUncommittedValue() + 1));

	//update sample frequency 
	pSampleFreqProp->updateValue(NDTS::RS2A::rs2aGlobalState.sampleFrequencyHz / (pSamplesDropProp->getUncommittedValue() + 1));
}


void SDKChangeHook::configureArray()
{
	// Don't attempt to configure the array until all the properties have their initial value
	if (pNumberOfElementsProp->getUncommittedValue() == -1 ||
		pApertureSizeProp->getUncommittedValue() == -1 ||
		pInvertArrayProp->getUncommittedValue() == -1 ||
		pPingPongProp->getUncommittedValue() == -1 ||
		pElementMultiplierProp->getUncommittedValue() == -1)
		return ;

	int apertureCodeToSize[RQ_ApertureSizeNumberOfValues] = {8, 16, 24, 32};
	int apertureSize = apertureCodeToSize[pApertureSizeProp->getUncommittedValue()];

	// Create the ping pong table - this works out the firing order and number of A per B
	PingPongTable table(pNumberOfElementsProp->getUncommittedValue(), 
		apertureSize,
		pElementMultiplierProp->getUncommittedValue(),
		(pPingPongProp->getUncommittedValue()!= 0)
		);

	// Update the number of A per B in the SDK and properties list
	NDTS_Boolean isOK = RQ_SetNumberOfAScansPerBScan(NDTS::RS2A::rs2aGlobalState.cardIndex, table.getAScansPerBScan()) ;
	_ASSERTE(isOK); isOK;
	pAScansPerBScanProp->updateValue(table.getAScansPerBScan());

	// Send the pingpong table to the RQ SDK
	for (int aScanIndex = 0; aScanIndex < table.getAScansPerBScan(); aScanIndex++)
	{
		//				        isOK = RQ_SetPingPongSequenceElement(boardIndex, e, x, reverse); 
		//RQ_SetPingPongSequenceElement(unsigned long cardIndex, unsigned long elementNumber, unsigned long sequencePosition, unsigned long effectiveElement) ;
		isOK = RQ_SetPingPongSequenceElement(rs2aGlobalState.cardIndex, 
			table.getSequencePosition(aScanIndex), 
			aScanIndex,
			table.getEffectivePosition(aScanIndex));  
		_ASSERTE(isOK); isOK;

	}

	// Flipping the array is done at a low level in the SDK and not in the ping pong tables.
	unsigned short numberOfElementsForFlipping = (pInvertArrayProp->getUncommittedValue()) ? pNumberOfElementsProp->getUncommittedValue() : 0;
	isOK = RQ_SetFlipArray(NDTS::RS2A::rs2aGlobalState.cardIndex, numberOfElementsForFlipping);
	_ASSERTE(isOK); isOK;

	// Inform the TCG 
	pTcg->setAScansPerBScan(table.getAScansPerBScan());


	// Update the min / max of the array properties
	// Todo: This isn't working properly when the aperture and multiplier are changed to large values
	pNumberOfElementsProp->updateMin(apertureSize);
	int maxApertureCode = RQ_AS32;
	if (pNumberOfElementsProp->getUncommittedValue() < 32)
		maxApertureCode = RQ_AS24;
	if (pNumberOfElementsProp->getUncommittedValue() < 24)
		maxApertureCode = RQ_AS16;
	if (pNumberOfElementsProp->getUncommittedValue() < 16)
		maxApertureCode = RQ_AS8;
	pApertureSizeProp->updateMax(maxApertureCode);
	pElementMultiplierProp->updateMax(table.getMaximumAScansPerBScan());

}

void SDKChangeHook::configureTriggering()
{
	RQ_Trigger triggerSelect = RQ_TInternal;

	// If not scanning or scanning with simple trigger mode
	if (pIsScanningProp->getUncommittedValue()== 0 || pScanTriggerModeProp->getUncommittedValue() == 0 )
	{
		if (pIsExtTriggerProp->getUncommittedValue() == 1)
			triggerSelect = RQ_TExternal;
	}
	else
	{
		// Scanning and not simple trigger
		// scanTriggerModeStrings {"Simple Trig",	"Encoder Trig",	"Free Running"};
		switch (pScanTriggerModeProp->getUncommittedValue())
		{
		case 0:
			// Simple
			_ASSERTE(0);
			break;
		case 1:
			triggerSelect = RQ_TEncoder;
			break;
		case 2:
			triggerSelect = RQ_TFreeRunning;
			break;
		}
	}
	NDTS_Boolean isOK = RQ_SetTriggerSelect(rs2aGlobalState.cardIndex, (RQ_Trigger)triggerSelect);
	_ASSERTE(isOK); isOK;

	// Update the PF_NotCurrentlyValid flags
	switch (pScanTriggerModeProp->getUncommittedValue())
	{
	case 0:
		// Simple triggering
		setNotCurrentlyValid(pEncoderToTriggerOffProp, true);
		setNotCurrentlyValid(pBScanRepRateSIProp, true);
		break;
	case 1:
		// Encoder triggering
		setNotCurrentlyValid(pEncoderToTriggerOffProp, false);
		setNotCurrentlyValid(pBScanRepRateSIProp, true);
		break;
	case 2:
		// Free running
		setNotCurrentlyValid(pEncoderToTriggerOffProp, true);
		setNotCurrentlyValid(pBScanRepRateSIProp, false);
		break;
	}

	// Flush any data that was collected since the last snapshot
	if (triggerSelect == RQ_TInternal)
		pTakeSnapshotProp->doEvent();
}


void SDKChangeHook::setNotCurrentlyValid(Property *pProp, bool isNotCurrentlyValid)
{
	int typeValue = pProp->getFlags();
	if (isNotCurrentlyValid)
		typeValue |= RS2A::PF_NotCurrentlyValid;
	else
		typeValue &= ~RS2A::PF_NotCurrentlyValid;

	dynamic_cast<PropertyInternal*>(pProp)->updateFlags(typeValue);
}
void SDKChangeHook::preCommit()
{
	// Values can arrive in any order.
	// To prevent checks or the SDK from causing an error, we force certain values 
	// before applying the changes in the group

	// First of all, set isScanning 
	if (pIsScanningProp->getHaveUncommittedChange())
		pIsScanningProp->setValue(pIsScanningProp->getUncommittedValue());

	// Set certain values to their safest limits to prevent memory overflow or over rep
	// getUncommittedValue will return the old value if there is no change in this change group
	int samplesDrop = pSamplesDropProp->getUncommittedValue();
	bool haveSamplesDropChange = pSamplesDropProp->getHaveUncommittedChange();
	int aScanRange = pAScanRangeProp->getUncommittedValue();
	bool haveAScanRangeChange = pAScanRangeProp->getHaveUncommittedChange();
	int aScanDelay = pAScanDelayProp->getUncommittedValue();
	bool haveAScanDelayChange = pAScanDelayProp->getHaveUncommittedChange();
	int aScanRepRate = pAScanRepRateProp->getUncommittedValue();
	bool haveAScanRepRateChange = pAScanRepRateProp->getHaveUncommittedChange();
	int bScanRepRate = pBScanRepRateSamplesProp->getUncommittedValue();
	int apertureSize = pApertureSizeProp->getUncommittedValue();
	int elementMultiplier = pElementMultiplierProp->getUncommittedValue();
	int isStandAloneRQ =  pIsStandAloneRQProp->getUncommittedValue();

	// Let the TCG know that we are in a pre commit so the curve doesn't get written every time
	// we change the range / delay
	pTcg->preCommitStart();
	// The post set update attempts to set the max and min of the range, rep rate etc.  
	// We don't want this to happen whilst we set these values wide open.
	postSetUpdateDisabled = true;

	// Set the samples drop to max to be sure the A Scan will fit into memory
	pSamplesDropProp->setValue(pSamplesDropProp->getMax());
	// Next turn the A Scan range right down so there is no chance of memory overflow
	pAScanRangeProp->setValue(pAScanRangeProp->getMin());
	// Turn down the delay and extend the rep rate so no chance of over rep errors
	pAScanDelayProp->setValue(pAScanDelayProp->getMin());
	pBScanRepRateSamplesProp->setValue(pBScanRepRateSamplesProp->getMax());
	pAScanRepRateProp->setValue(pAScanRepRateProp->getMax());


	// Set the array to safe values so any value may be set without array shape errors
	pApertureSizeProp->setValue(0);
	pElementMultiplierProp->setValue(pElementMultiplierProp->getMin());


	// It should now be safe to set the number of elements and then restore the other array values
	// As these settings are applied, the min and max of dependent settings can now be updated.
	postSetUpdateDisabled = false;

	// If we are selecting stand alone RQ mode, override the array settings
	// IW can set stand alone mode during a group commit in which the old array settings also get sent.
	// We therefore need to set the stand alone settings here as well as in the post set update
	if (isStandAloneRQ)
	{
		configureStandAloneRQ();
	}
	else
	{
		// Not stand alone RQ mode - send the values that the application asked for
		pNumberOfElementsProp->setValue(pNumberOfElementsProp->getUncommittedValue());
		pApertureSizeProp->setValue(apertureSize);
		pElementMultiplierProp->setValue(elementMultiplier);
	}


	// The array shape is now correct and range based values at safe limits.
	// It should be safe to set values in any order now.  If anything gets clamped or fails now, the 
	// settings in the group must be inconsistent.
	// Restore the saved values then exit so the normal commit process can write the other properties in the group


	// If only one value changes in a commit, it should be the one that fails
	// if it is set out of range, so set the unchanged values first.
	if (!haveSamplesDropChange)
		pSamplesDropProp->setValue(samplesDrop);
	if (!haveAScanRangeChange)
		pAScanRangeProp->setValue(aScanRange);
	if (!haveAScanDelayChange)
		pAScanDelayProp->setValue(aScanDelay);
	if (!haveAScanRepRateChange)
		pAScanRepRateProp->setValue(aScanRepRate);

	// Now set the changed values
	pSamplesDropProp->setValue(samplesDrop);
	pAScanRangeProp->setValue(aScanRange);
	pAScanDelayProp->setValue(aScanDelay);
	pAScanRepRateProp->setValue(aScanRepRate);

	// The B Scan Rep Rate is always a lower priority than the number of elements, so if the number of elements is
	// increased, the B Scan rep rate may be updated, even if the number of elements is the only change
	pBScanRepRateSamplesProp->setValue(bScanRepRate);

	pTcg->preCommitComplete();

}

void SDKChangeHook::setTcg(TCGInternal *pTcg)
{
	this->pTcg = pTcg;
}

void SDKChangeHook::configureStandAloneRQ()
{
	// Set 8 elements with aperture of 8 and multiplier of 8.  This gives a single A scan per B Scan
	pElementMultiplierProp->setValue(1);
	pApertureSizeProp->setValue(RQ_AS8);
	pNumberOfElementsProp->setValue(8);
}

