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

namespace NDTS
{
	namespace SCO
	{
	SCO_API SIShadowPropertyInternal::SIShadowPropertyInternal(char *pShortName, char *pLongName, int flags, SI::BaseUnit baseUnit, SI::Prefix prefix, IntPropertyInternal *pShadowedProperty, SIPropertyInternal *pScaleProperty, Relationship relationshipType, double intToSamplesMultiplier): 
												SIPropertyInternal(pShortName, pLongName, flags, baseUnit, prefix)

	{
		this->pShadowedProperty = pShadowedProperty;
		if (pShadowedProperty)
			pShadowedProperty->addShadow(this);
		this->pScaleProperty = pScaleProperty;
		if (pScaleProperty)
			pScaleProperty->addShadow(this);
		_ASSERTE(relationshipType == ProportionalRelationship || relationshipType == InverseRelationship);
		this->relationshipType = relationshipType;
		uncommittedValue = -1.0;
		haveUncommittedChange = false;
		this->intToSamplesMultiplier = intToSamplesMultiplier;
	}

	SCO_API SIShadowPropertyInternal::~SIShadowPropertyInternal(void)
	{
	}
	bool SIShadowPropertyInternal::getHaveUncommittedChange()
	{
		return haveUncommittedChange;
	}

	double SIShadowPropertyInternal::getUncommittedSIValue()
	{
		return (haveUncommittedChange) ? uncommittedValue : getSIValue();
	}

	double SIShadowPropertyInternal::getSIValue()
	{
		return scaleForGet(pShadowedProperty->getUncommittedValue());
	}

	double SIShadowPropertyInternal::getSIMin()
	{
		return scaleForGet(pShadowedProperty->getMin());
	}

	double SIShadowPropertyInternal::getSIMax()
	{
		return scaleForGet(pShadowedProperty->getMax());
	}


	double SIShadowPropertyInternal::getScale()
	{
		double scale = intToSamplesMultiplier;   // Apply the fixed multiplier, e.g. 80/255 for gain or default of 1.0 
		if (pScaleProperty)  // Apply the property to scale by, e.g. the scan pitch for a scan width or effectively a default of 1.0 if there is no pScaleProperty
		{
			scale = scale * pScaleProperty->getSIValue();
		}
		return scale;
	}

	double SIShadowPropertyInternal::scaleForGet(int value)
	{
		double rawResult = 0.0;
		if (relationshipType == ProportionalRelationship)
		{
			rawResult = getScale() * (double)value;
		}
		else
		{
			_ASSERTE(relationshipType == InverseRelationship);
			rawResult = getScale() / (double)value;
		}
		// Round to this many significant figures to prevent excessive decimal places in edit boxes
/*		Doesn't work correctly for inverse props like the A Scan rep rate
		const double decPlaces = 4;
		double shift = floor(log10(rawResult));
		shift = decPlaces + 1 - shift;
		double mult = pow(10.0, shift);
		rawResult = floor(mult * rawResult) / mult;
		*/
		return rawResult;
	}
	
	int  SIShadowPropertyInternal::scaleForSet(double value)
	{
		double scaledVal = 0.0;
		double scale = getScale();

		if (relationshipType == ProportionalRelationship)
		{
			// Setting a property, convert from float to int - do the inverse of the scaleType which specifies the conversion from int to float
			scaledVal = value / scale;
		}
		else
		{
			_ASSERTE(relationshipType == InverseRelationship);
			scaledVal = scale / value ;
		}

		// Rounding is to the nearest value rather than truncation
		double d = floor(scaledVal + 0.5);
		// If d overflows the int range, the cast to int will give a corrupted value or zero
		if (d > INT_MAX)
			d = INT_MAX;
		if (d < INT_MIN)
			d = INT_MIN;
		int iVal = (int)d;
		return iVal;
	}

	// Return true if value differs from the int being shadowed by a "significant" amount
	bool SIShadowPropertyInternal::isValueSignificantlyDifferentFromShadow(double value)
	{
		// Get the uncommitted value because in a group commit, we are writing to an Int that is still in a group change.
		double newValueSI = scaleForGet(pShadowedProperty->getUncommittedValue());
		double newValueScaled = getSIUnit()->baseToPrefix(newValueSI);
		double  originalValueScaled = getSIUnit()->baseToPrefix(value);
		return (abs(originalValueScaled - newValueScaled) > 0.000001);
	}

	void SIShadowPropertyInternal::setSIValue(double value)
	{
		if (!isValueSignificantlyDifferentFromShadow(value) && !haveUncommittedChange)
			return;
		// If in a group of changes, remember the value but don't range check or apply it
		if (pParent->isInSIGroupChange())
		{
			uncommittedValue = value;
			haveUncommittedChange = true;
			return;
		}
		haveUncommittedChange = false;
		pShadowedProperty->setValue(scaleForSet(value));
		
		// Generate a change event if there has been any rounding
		if (isValueSignificantlyDifferentFromShadow(value))
		{
			createChangeItem(CTValue);
			pLog->addMessageToLog(NDTS_LLInfo, "Set %s to %f %s was rounded / clamped to %f %s",	
				pShortName,	
				getSIUnit()->baseToPrefix(value), 
				getSIUnit()->getName(), 
				getSIUnit()->baseToPrefix(scaleForGet(pShadowedProperty->getUncommittedValue())) , 
				getSIUnit()->getName());
		}
		else
		{
			createChangeItemForShadowingProperties(CTValue);
		}
	}
}};
