#include <crtdbg.h>

#include "NDTS2100Internal.h"
#include "NDTS2100Errors.h"
#include "NDTSError.h"
#include "PropertiesInterface.h"
#include "PropertyInternal.h"
#include "IntPropertyInternal.h"
#include "EnumPropertyInternal.h"
#include "SIShadowPropertyInternal.h"
#include "EventPropertyInternal.h"
#include "PropertiesCollection.h"
#include "TCG.h"
#include "ChangeHook.h"
#include "NonRegisterHardwareAccessClass.h"
#include "RegisterBasedHardwareAccess.h"
#include "SnapshotProtected.h"

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

ChangeHook::ChangeHook(void)
{
	pTcg = 0;
}

ChangeHook::~ChangeHook(void)
{
}

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

bool ChangeHook::isOkToSet(PropertyInternal* pProperty)
{
	if (pProperty != pIsScanningProp &&  pIsScanningProp->getValue() != 0)
	{
		// Check for takeSnapshot - need to find the correct property as it is per card
		EventPropertyInternal *pEventProp = dynamic_cast<EventPropertyInternal*>(pProperty);
		if (pEventProp)
		{
			int card = dynamic_cast<NDTS2100HardwareAccess*>(pEventProp->getHardwareAccessObject())->getCardNumber();
//			if (pProperty == pTakeSnapshotProp[card])
			if (pProperty == pTakeSnapshotProp)
			{
				return true;
			}
		}

		NDTS2100_HandleNDTS2100Error(NDTS2100_PropertySetWhenScanning);
		return false;
	}
	return true;
}

void ChangeHook::postSetUpdate(IntPropertyInternal *pProp)
{
	// 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
	NDTS2100HardwareAccess *pHardwareAccess = dynamic_cast<NDTS2100HardwareAccess*>(pProp->getHardwareAccessObject());

	int card = pHardwareAccess->getCardNumber();
	int step = pHardwareAccess->getStepNumber();
	int gate = pHardwareAccess->getGateNumber();
	// If card or step = -1, we are initialising.
	// If this is an IO property, its index 0 and index 1 don't refer to cards / steps.
	// Hooks are not required for any objects that are not card / step indexed (e.g. hooks not used on IO).
	if (card < 0 || card >= maxCards || step < 0 || step >= maxSteps)
		return;

	// Lock out the data collection thread until all the settings have been applied
	// Especially important when the number of steps are changed 
	StackBasedLock lock(card);

	if (pProp == pNumberOfStepsProp[card])
		updateNumberOfSteps(card);
	else if (pProp == pAScanRangeProp[card][step] || pProp == pAScanRepRateProp[card][step] || pProp == pAScanDelayProp[card][step] || pProp == pSamplesDividerProp[card][step])
		updateRangeDelayRepMax(card, step);
	// Keep the TCG in sync with the settings
	else if (pProp == pAScanRangeProp[card][step])
		globalState.pTcg->setSamplesPerAScan(card, step, pProp->getValue());
	else if (pProp == pSamplesDividerProp[card][step])
		globalState.pTcg->setSamplesDropPerAScan(card, step, pProp->getValue());
	else if (pProp == pInterfaceTriggeredTCG[card][step])
		globalState.pTcg->setIsInterfaceTriggered(card, step, (pProp->getValue() == 1));
	else if (pProp == pSimpleGainOrTCG[card][step])
		globalState.pTcg->setIsSimpleGain(card, step, (pProp->getValue() == 0));
	else if (pProp == pSimpleGain[card][step])
		globalState.pTcg->setSimpleGain(card, step, pProp->getValue());
	// Update the triggering which marks some controls read only
	else if (pProp == pIsScanningProp || pProp == pScanTriggerModeProp[card])
		configureTriggering(card);
	else if (pProp == pIsFullAScanCapture[card])
		setIsFullAScanCapture(card);
	else if (pProp == pGateEnableProp[card][step][gate] || 
		     pProp == pGateDistanceMetricProp[card][step][gate] || 
		     pProp == pGateStoreOnlyWhenTriggeredProp[card][step][gate] || 
		     pProp == pGateTriggerGateProp[card][step][gate])
		configureGate(card, step, gate);
}


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

void ChangeHook::updateNumberOfSteps(int card)
{
	// If increasing the number of steps, set the new steps to default values
	// There is a theoretical possiblity that this may cause a memory overflow during
	// a group commit if e.g. the first step has a very large range and the new steps a range smaller
	// than the default


	// The steps are all set with default values at start up so we don't need to re-send them here
	/*
	for (int step = globalState.numberOfSteps[card]; step < pNumberOfStepsProp[card]->getValue(); step++)
	{
		pAScanRangeProp[card][step]->setValue(defaultRangeForNewStep);
		pAScanDelayProp[card][step]->setValue(0);
		pAScanRepRateProp[card][step]->setValue(defaultRepRateForNewStep);
	}
	*/
	globalState.numberOfSteps[card] = pNumberOfStepsProp[card]->getValue();

	updateSampleParameters(card);
}

// 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 ChangeHook::updateRangeDelayRepMax(int card, int step)
{
	// Initialising - don't update values until all properties are valid.
	// Use getUncommittedValue throughout this function as it is called during the post commit hook.
	if (card == -1 || step == -1 || pNumberOfStepsProp[card]->getUncommittedValue() == -1)
		return;
	_ASSERTE(card >= 0 && card < maxCards);
	_ASSERTE(step >= 0 && step < maxSteps && step < pNumberOfStepsProp[card]->getUncommittedValue());
	// Initialising - don't update values until all properties are valid.
	if (pAScanRangeProp[card][step]->getUncommittedValue() == -1 || pAScanDelayProp[card][step]->getUncommittedValue() == -1 || 
		pAScanRepRateProp[card][step]->getUncommittedValue() == -1 || pNumberOfStepsProp[card]->getUncommittedValue() == -1 || 
		pBScanRepRateSamplesProp[card]->getUncommittedValue() == -1 || pSamplesDividerProp[card][step]->getUncommittedValue() == -1)
		return;

	// Bottom bit of A Scan range is always zero
	int range = pAScanRangeProp[card][step]->getUncommittedValue();
	range = AScanRangeReg::getAdjustedRange(range);
	pAScanRangeProp[card][step]->setValue(range);

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

	int minAScanRangeMax = aScanRangeMax;

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

	// The A Scan must also fit into available memory - this looks like it is RS2A code that assumes same A Scan range per step
	// 2100 should have ample memory anyway
	int numSteps = pNumberOfStepsProp[card]->getUncommittedValue();
/*
	int otherMems = numSteps * 32 + 4 + numberOfEncoders * 4;
	if ((aScanMemoryBankSize - otherMems) / ( numSteps  * 2) < minAScanRangeMax)
		minAScanRangeMax = (aScanMemoryBankSize  - otherMems) / (numSteps  * 2);
		*/

	pAScanRangeProp[card][step]->updateMax(minAScanRangeMax);		

	pAScanDelayProp[card][step]->updateMax(spareSamples + pAScanDelayProp[card][step]->getUncommittedValue());
	pAScanRepRateProp[card][step]->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[card]->updateMin((activeSamples + spareSamples) * (pNumberOfStepsProp[card]->getUncommittedValue() + 1));


	// Update the globalState that defines the memory layout for the data collection
	if (globalState.dataCollectionValues[card].aScanSampleValuesThisStep[step] == pAScanRangeProp[card][step]->getUncommittedValue())
		return;

	globalState.dataCollectionValues[card].aScanSampleValuesThisStep[step] = pAScanRangeProp[card][step]->getUncommittedValue();
	globalState.dataCollectionValues[card].aScanOffsetThisStep[0] = 4; // Header
	
	for (int i = 1; i < numSteps; i++)
	{
		globalState.dataCollectionValues[card].aScanOffsetThisStep[i] = globalState.dataCollectionValues[card].aScanOffsetThisStep[i-1] +
			bytesPerSample * globalState.dataCollectionValues[card].aScanSampleValuesThisStep[i-1] + // size of last ascan
											  crcBytes + 4 ;  // CRC of last scan + header of this scan (No oversampling on 2100 - if there were, add oversampling headers)
	}
	globalState.dataCollectionValues[card].aScanBytesPerBScan = globalState.dataCollectionValues[card].aScanOffsetThisStep[numSteps] + // Previous steps
			bytesPerSample * globalState.dataCollectionValues[card].aScanSampleValuesThisStep[numSteps] + crcBytes +  // last step
			crcBytes + maxEncoders * 4 ; //  Sequence CRC and Encoder data 

	updateSampleParameters(card);
}

void ChangeHook::configureTriggering(int card)
{
	_ASSERTE(card >= 0 && card < maxCards);

	Reg sequenceTriggerMode(0x00305038, card, 0, 0);

	// Update the PF_NotCurrentlyValid flags
	switch (pScanTriggerModeProp[card]->getUncommittedValue())
	{
	case 0:
		// Simple triggering
		setNotCurrentlyValid(pEncoderToTriggerOffProp[card], true);
		setNotCurrentlyValid(pBScanRepRateSIProp[card], true);
		sequenceTriggerMode.setValue(0);
		globalState.dataCollectionValues[card].isSoftwareTriggered = true;
		break;
	case 1:
		// Encoder triggering
		setNotCurrentlyValid(pEncoderToTriggerOffProp[card], false);
		setNotCurrentlyValid(pBScanRepRateSIProp[card], true);
		sequenceTriggerMode.setValue(0x4);  // Bit 2
		globalState.dataCollectionValues[card].isSoftwareTriggered = false;
		break;
	case 2:
		// Free running
		setNotCurrentlyValid(pEncoderToTriggerOffProp[card], true);
		setNotCurrentlyValid(pBScanRepRateSIProp[card], false);
		sequenceTriggerMode.setValue(0x2);  // Bit 1
		globalState.dataCollectionValues[card].isSoftwareTriggered = false;
		break;
	case 3:
		// Externally triggered
		setNotCurrentlyValid(pEncoderToTriggerOffProp[card], true);
		setNotCurrentlyValid(pBScanRepRateSIProp[card], false);
		sequenceTriggerMode.setValue(0x1);  // Bit 0
		globalState.dataCollectionValues[card].isSoftwareTriggered = false;
		break;
	}

	// Flush any data that was collected since the last snapshot
	//	pTakeSnapshotProp[card]->doEvent();
	if (globalState.isInitialised)
		pTakeSnapshotProp->doEvent();
}


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

	dynamic_cast<PropertyInternal*>(pProp)->updateFlags(typeValue);
}
void ChangeHook::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
	// getHaveUncommittedChange will return the old value if there is no change in this change group
	// Work through one card at a time as the settings of a card won't affect another card
	int aScanRange[maxSteps];
	int aScanDelay[maxSteps];
	int aScanRepRate[maxSteps];
	int bScanRepRate;
	int numberOfSteps;

	for (int card=0; card < globalState.numberOfCards; card++)
	{
		// Save the current values
		numberOfSteps = pNumberOfStepsProp[card]->getUncommittedValue();
		bScanRepRate = pBScanRepRateSamplesProp[card]->getUncommittedValue();
		// Go through max steps because the number of steps may have changed
		for (int step=0; step<maxSteps; step++)
		{
			aScanRange[step] = pAScanRangeProp[card][step]->getUncommittedValue();
			aScanDelay[step] = pAScanDelayProp[card][step]->getUncommittedValue();
			aScanRepRate[step] = pAScanRepRateProp[card][step]->getUncommittedValue();
		}

		// Set the correct new number of steps before applying anything else
		pNumberOfStepsProp[card]->setValue(numberOfSteps);

		// Apply values that won't cause over rep errors and reduce the chance of out of mem errors
		for (int step=0; step<pNumberOfStepsProp[card]->getValue(); step++)
		{
			// Don't care about pSamplesDividerProp and fitting into memory as it is changed on 2100
			//pSamplesDividerProp[step]->setValue(pSamplesDividerProp[card][step]->getMax());

			// Next turn the A Scan range right down so there is no chance of memory overflow
			pAScanRangeProp[card][step]->setValue(pAScanRangeProp[card][step]->getMin());
			// Turn down the delay and extend the rep rate so no chance of over rep errors
			pAScanDelayProp[card][step]->setValue(pAScanDelayProp[card][step]->getMin());
			pBScanRepRateSamplesProp[card]->setValue(pBScanRepRateSamplesProp[card]->getMax());
			pAScanRepRateProp[card][step]->setValue(pAScanRepRateProp[card][step]->getMax());
		}

		// 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 are probably inconsistent.
		// Restore the saved values in the order below
		// then exit so the normal commit process can write the other properties in the group
		for (int step=0; step<pNumberOfStepsProp[card]->getValue(); step++)
		{
			//pSamplesDividerProp[card][step]->setValue(samplesDrop);
			pAScanRangeProp[card][step]->setValue(aScanRange[step]);
			pAScanRepRateProp[card][step]->setValue(aScanRepRate[step]);
			pAScanDelayProp[card][step]->setValue(aScanDelay[step]);
			pBScanRepRateSamplesProp[card]->setValue(bScanRepRate);
		}
	}
}

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


void ChangeHook::setIsFullAScanCapture(unsigned long card)
{
	// Can't set the full A mode until the data collection has been initialised
	// DC will default to no full A during init so it will remain in sync
	bool isFullA = (pIsFullAScanCapture[card]->getValue() == 1)? true : false;
	if (!globalState.isInitialised)
	{
		_ASSERTE(!isFullA);
		return;
	}

	globalState.dataCollectionValues[card].isFullACapture = isFullA;
	DC_SetFullAScanCapture(card, (isFullA) ? NDTS_BTrue : NDTS_BFalse);
}

void ChangeHook::configureGate(unsigned long card, unsigned long step, unsigned long gate)
{
	bool hasNumGatesChanged = false;
	if (globalState.dataCollectionValues[card].ppGateSettings[step][gate].isGateEnabled != pGateEnableProp[card][step][gate]->getUncommittedValue())
	{
		globalState.dataCollectionValues[card].ppGateSettings[step][gate].isGateEnabled = (pGateEnableProp[card][step][gate]->getUncommittedValue() == 1) ? NDTS_BTrue : NDTS_BFalse;
		hasNumGatesChanged = true;
	}
	// Keep in sync with gateDistanceMetricNames
	globalState.dataCollectionValues[card].ppGateSettings[step][gate].isDistanceMetricPeak = (pGateDistanceMetricProp[card][step][gate]->getUncommittedValue() == 2) ? NDTS_BTrue : NDTS_BFalse;
	globalState.dataCollectionValues[card].ppGateSettings[step][gate].storeGateOnlyWhenTriggered = (pGateStoreOnlyWhenTriggeredProp[card][step][gate]->getUncommittedValue() == 1) ? NDTS_BTrue : NDTS_BFalse;;
	// Keep in sync with triggerGateNames
	globalState.dataCollectionValues[card].ppGateSettings[step][gate].triggerGate = pGateTriggerGateProp[card][step][gate]->getUncommittedValue();

	if (!hasNumGatesChanged)
		return;

	// If the number of gates has changed, recalculate the memory layout for data collection.
	// Find out how many gates are enabled in this step
	int enabledGates = 0;
	for (int i=0; i<maxGates; i++)
	{
		if (globalState.dataCollectionValues[card].ppGateSettings[step][i].isGateEnabled)
			enabledGates++;
	}

	// Work out gate offsets for this step
	globalState.dataCollectionValues[card].gateCountThisStep[step] = enabledGates;
	globalState.dataCollectionValues[card].gateOffsetThisStep[0] = 4;
	int numSteps = pNumberOfStepsProp[card]->getUncommittedValue();
	for (int i=1; i<numSteps; i++)
	{
		globalState.dataCollectionValues[card].gateOffsetThisStep[i] = globalState.dataCollectionValues[card].gateOffsetThisStep[i-1] + 
			4 + gateResultBytes * globalState.dataCollectionValues[card].gateCountThisStep[i-1];  // Our header + last step's size
	}
	globalState.dataCollectionValues[card].gateBytesPerBScan = globalState.dataCollectionValues[card].gateOffsetThisStep[numSteps-1] + 
			gateResultBytes * globalState.dataCollectionValues[card].gateCountThisStep[numSteps-1] +   // last step's size 
			crcBytes + maxEncoders * 4; // Gate result CRC + encoder bytes

	updateSampleParameters(card);
}

void ChangeHook::updateSampleParameters(unsigned long card)
{
	int aMaxBPerBank = aScanMemoryBankSize / (globalState.dataCollectionValues[card].aScanBytesPerBScan) - 100;  // Take away a small safety margin
	int cMaxBPerBank = cScanMemoryBankSize / (globalState.dataCollectionValues[card].gateBytesPerBScan) - 100;
	int maxBPerBank = (aMaxBPerBank < cMaxBPerBank) ? aMaxBPerBank : cMaxBPerBank;
//	NDTS_LogLine(NDTS_LLErr, "updateMaxBPerBank aMaxBPerBank %d, cMaxBPerBank %d, maxBPerBank %d", aMaxBPerBank, cMaxBPerBank, maxBPerBank);
	Reg bPerBankReg(0x00305024, card);
	bPerBankReg.setValue(maxBPerBank);

	if (globalState.pUpdateSampleParametersHook)
		globalState.pUpdateSampleParametersHook(card);

}
