#include <limits.h>
#include <crtdbg.h>
#include "IntPropertyInternal.h"
#include "GroupPropertyInternal.h"
#include "Log.h"
#include "NdtsError.h"

namespace NDTS
{
	namespace SCO
	{
	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, int defaultValue, int min, int max, int primaryIndex, NDTS_Boolean (*pSetFunction)(unsigned long, unsigned long)): PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->min = min;
		this->max = max;
		staticMin = min;
		staticMax = max;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = pSetFunction;
		this->pGetFunction = 0;
		this->primaryIndex = primaryIndex;
		this->defaultValue = defaultValue;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		getResultCanBeNegative = false;
		pHardwareAccessObject = 0;
		hardwareAccessClassUsage = useFunctionPointers;
		this->enumToValueMap = 0;
		this->numberOfEnums = 0;
	}

	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, int primaryIndex,  NDTS_Boolean (*pGetFunction)(unsigned long, unsigned long*), bool resultCanBeNegative) : PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->getResultCanBeNegative = resultCanBeNegative;
		this->min = -1;
		this->max = -1;
		staticMin = -1;
		staticMax = -1;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = 0;
		this->pGetFunction = pGetFunction;
		this->primaryIndex = primaryIndex;
		this->defaultValue = -1;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		pHardwareAccessObject = 0;
		hardwareAccessClassUsage = useFunctionPointers;
		this->enumToValueMap = 0;
		this->numberOfEnums = 0;
	}

	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, int defaultValue, int min, int max, int primaryIndex, NDTS_Boolean (*pSetFunction)(unsigned long, unsigned long), NDTS_Boolean (*pGetFunction)(unsigned long, unsigned long*)): PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->min = min;
		this->max = max;
		staticMin = min;
		staticMax = max;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = pSetFunction;
		this->pGetFunction = pGetFunction;
		this->primaryIndex = primaryIndex;
		this->defaultValue = defaultValue;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		getResultCanBeNegative = false;
		pHardwareAccessObject = 0;
		hardwareAccessClassUsage = useFunctionPointers;
		this->enumToValueMap = 0;
		this->numberOfEnums = 0;
	}


	// Property that uses a hardware access class rather than function pointers
	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, int defaultValue, int min, int max, HardwareAccessClass *pHardwareAccessObject, HardwareAccessClassUsage hardwareAccessClassUsage) : PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->min = min;
		this->max = max;
		staticMin = min;
		staticMax = max;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = 0;
		this->pGetFunction = 0;
		this->primaryIndex = 0;
		this->defaultValue = defaultValue;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		// Hardware access class get functions default to being able to return negative values
		getResultCanBeNegative = true;
		this->pHardwareAccessObject = pHardwareAccessObject ;
		this->hardwareAccessClassUsage = hardwareAccessClassUsage;
		this->pHardwareAccessObject->pAssociatedProperty = this;
		this->enumToValueMap = 0;
		this->numberOfEnums = 0;
	}


	// Property that uses a hardware access class rather than function pointers
	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, int defaultValue, int min, int max, int *enumToValueMap, int numberOfEnums, HardwareAccessClass *pHardwareAccessObject, HardwareAccessClassUsage hardwareAccessClassUsage) : PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->min = min;
		this->max = max;
		staticMin = min;
		staticMax = max;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = 0;
		this->pGetFunction = 0;
		this->primaryIndex = 0;
		this->defaultValue = defaultValue;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		// Hardware access class get functions default to being able to return negative values
		getResultCanBeNegative = true;
		this->pHardwareAccessObject = pHardwareAccessObject ;
		this->hardwareAccessClassUsage = hardwareAccessClassUsage;
		this->pHardwareAccessObject->pAssociatedProperty = this;
		this->enumToValueMap = enumToValueMap;
		this->numberOfEnums = numberOfEnums;
	}



	// Read only property that uses a hardware access class
	SCO_API IntPropertyInternal::IntPropertyInternal(char *pShortName, char *pLongName, int flags, HardwareAccessClass *pHardwareAccessObject) : PropertyInternal(pShortName, pLongName, flags)
	{
		isVisible = true;
		this->min = -1;
		this->max = -1;
		staticMin = -1;
		staticMax = -1;
		// Initialise to -1 so that the value is applied as a change during initialisation
		this->value = -1;
		this->pSetFunction = 0;
		this->pGetFunction = 0;
		this->primaryIndex = 0;
		this->defaultValue = -1;
		uncommittedValue = -1;
		haveUncommittedChange = false;
		// Hardware access class get functions default to being able to return negative values
		getResultCanBeNegative = true;
		this->pHardwareAccessObject = pHardwareAccessObject ;
		this->hardwareAccessClassUsage = writeIsErrorReadFromHardware;
		this->pHardwareAccessObject->pAssociatedProperty = this;
		this->enumToValueMap = 0;
		this->numberOfEnums = 0;
	}
	

	SCO_API IntPropertyInternal::~IntPropertyInternal(void)
	{
		delete pHardwareAccessObject;
	}

	SCO_API void IntPropertyInternal::initComplete(void)
	{
		PropertyInternal::initComplete();
		if ((hardwareAccessClassUsage == useFunctionPointers && pSetFunction) ||
			hardwareAccessClassUsage == writeToHardwareReadFromCachedValue || 
			hardwareAccessClassUsage == writeToHardwareReadFromHardware || 
			hardwareAccessClassUsage == writeToHardwareEvenIfNoChangeReadFromHardware)
			setValue(defaultValue);
		else
			updateValue(defaultValue);
	}

	int IntPropertyInternal::getValue()
	{
		_ASSERTE(!pParent->isInIntGroupChange());
		_ASSERTE(!haveUncommittedChange);
		if (pGetFunction)
		{
			unsigned long result;
			if (!pGetFunction(primaryIndex, &result) || (!getResultCanBeNegative && (int)result < 0))
			{
				value = 0;
				pLog->addMessageToLog(NDTS_LLDebug, "Get function failed for %s", pShortName);
			}
			value = result;
		}
		if (hardwareAccessClassUsage == writeToHardwareReadFromHardware || 
			hardwareAccessClassUsage == writeIsErrorReadFromHardware ||
			hardwareAccessClassUsage == writeToHardwareEvenIfNoChangeReadFromHardware)
		{
			value = pHardwareAccessObject->getValue();
			if (value < 0 && !getResultCanBeNegative)
			{
				value = 0;
				pLog->addMessageToLog(NDTS_LLDebug, "Get function failed for %s", pShortName);
			}
		}

		// If there is an enum map, look up the value and return the enum rather than the register value
		if (!enumToValueMap)
			return value;

		int index=0;
		for (index=0; index<numberOfEnums; index++)
		{
			if (value == enumToValueMap[index])
				break;
		}
		// Has the enum been found?
		_ASSERTE(value == enumToValueMap[index]);
		return index;
	}

	bool IntPropertyInternal::getHaveUncommittedChange()
	{
		return haveUncommittedChange;
	}

	int IntPropertyInternal::getUncommittedValue()
	{
		return (haveUncommittedChange) ? uncommittedValue : value;
	}


	// Set the requested value to the SDK or cache it if a group change is in progress
	void IntPropertyInternal::setValue(int value)
	{
		// No change - return immediately.  This is useful for applications that frequently re send all their property values
		if (!haveUncommittedChange && hardwareAccessClassUsage != writeToHardwareEvenIfNoChangeReadFromHardware)
		{
			if (!enumToValueMap)
			{
				if (value == this->value)
					return;
			}
			else
			{
				_ASSERTE(value >= 0 && value < numberOfEnums);
				if (enumToValueMap[value]== this->value)
					return;
			}
		}


		// If in a group of changes, remember the value but don't range check or apply it
		if (pParent->isInIntGroupChange())
		{
			uncommittedValue = value;
			haveUncommittedChange = true;
			return;
		}
		haveUncommittedChange = false;

		// Null set function pointer means the property is read only
		if ((hardwareAccessClassUsage == useFunctionPointers  && pSetFunction == 0)
			|| hardwareAccessClassUsage== writeIsErrorReadFromHardware)
		{
			pLog->addError(SCO_PropertyIsReadOnly);	
			return;
		}



		if (!pParent->isOkToSet(this))
		{
			// Write the existing value as a property change because the app needs to know that the 
			// value has not been updated (i.e. has been changed back to the original value)
			createChangeItem(CTValue);
			return;
		}

		int clampedValue  = value;
		// The error is logged when the clamped value is committed, in case the low level SDK call fails for another reason.
		if (value < min)
			clampedValue = min;
		if (value > max)
			clampedValue = max;

		if (enumToValueMap)
		{
			_ASSERTE(value >= 0 && value < numberOfEnums);
			clampedValue = enumToValueMap[value];
		}

		// Call the appropriate set function
		if (hardwareAccessClassUsage == useFunctionPointers)
		{
			if (!pSetFunction(primaryIndex, clampedValue))
			{
				pLog->addMessageToLog(NDTS_LLDebug, "SET %s = %d", pShortName, clampedValue);
				// Lower level SDK returned false means the value can't be set. Value should be unchanged.
				// We return false to tell the app there is a message in the error log that needs processing.
				// Write the existing value as a property change because the app needs to know that the 
				// value has not been updated
				createChangeItem(CTValue);
				return;
			}
		}
		else
		{
			_ASSERTE(hardwareAccessClassUsage == writeToHardwareReadFromCachedValue || 
				hardwareAccessClassUsage == writeToHardwareReadFromHardware || 
				hardwareAccessClassUsage== writeToHardwareEvenIfNoChangeReadFromHardware);
			if (!pHardwareAccessObject->setValue(clampedValue))
			{
				pLog->addMessageToLog(NDTS_LLDebug, "SET %s = %d", pShortName, clampedValue);
				createChangeItem(CTValue);
				return;
			}
		}

		this->value = clampedValue;

		bool noChanges = true;
		// Value is as the app requested
		if ((enumToValueMap && (value < min || value > max)) || 
			(!enumToValueMap && value != clampedValue))
		{
			noChanges = false;
			// We only generate a value change event if the value has been clamped, not if succesfully applied.
			createChangeItem(CTValue);
			pLog->addError(SCO_PropertyValueOutOfRange, NDTS_LLDebug);
 			pLog->addMessageToLog(NDTS_LLInfo, "%s value %d was clamped to %d",	pShortName,	value, clampedValue);
		}
		else
		{
			// Always generate change event for shadows
			createChangeItemForShadowingProperties(CTValue);
		}

		pParent->postSetUpdate(this);
	}

	int IntPropertyInternal::getMin()
	{
		return min;

	}
	int IntPropertyInternal::getMax()
	{
		return max;
	}

	// Update and add to change log - for use with pPostSetUpdateHook - not for use with application
	void IntPropertyInternal::updateValue(int newValue)
	{
		_ASSERTE(newValue >= min);
		_ASSERTE(newValue <= max);
		if (newValue == value && !haveUncommittedChange)
			return;

		// updateValue should only be used for read only properties that don't need to set anything in the SDK
		_ASSERTE(pSetFunction == 0);
		_ASSERTE(hardwareAccessClassUsage== writeIsErrorReadFromHardware || 
			hardwareAccessClassUsage == useFunctionPointers || 
			hardwareAccessClassUsage == readFromCacheUpdateAfterHardwareInit);

		value = newValue;
		createChangeItem(CTValue);
		pLog->addMessageToLog(NDTS_LLDebug, "%s VALUE Updated to %d", pShortName, value);
	}

	void IntPropertyInternal::updateMin(int newMin)
	{
		_ASSERTE((newMin <= value || value == -1) && newMin <= max && newMin <= staticMax);
		if (newMin < staticMin)
			newMin = staticMin;

		if (newMin == min)
			return ;

		min = newMin;
		createChangeItem(CTBounds);
		pLog->addMessageToLog(NDTS_LLDebug, "%s MIN changed to %d", pShortName, min);
	}

	void IntPropertyInternal::updateMax(int newMax)
	{
		_ASSERTE(newMax >= value && newMax >= min && newMax >= staticMin);
		if (newMax > staticMax)
			newMax = staticMax;

		if (newMax == max)
			return;

		max = newMax;
		createChangeItem(CTBounds);
		pLog->addMessageToLog(NDTS_LLDebug, "%s Max changed to %d", pShortName, max);
		return;
	}
	
	HardwareAccessClass *IntPropertyInternal::getHardwareAccessObject()
	{
		return pHardwareAccessObject;
	}
}};
