/******************************************************************************
 *                                                                         
 *               (C) COPYRIGHT Pacific MindWorks, Inc. 2001-2005
 *                          All rights reserved.
 *
 *****************************************************************************/

#ifndef __NTLCOERCION_INCLUDED
#define __NTLCOERCION_INCLUDED

#include <atlstr.h>
#include <atlcoll.h>

///////////////////////////////////////////////////////////////////////////////
//
//	CCoercionLog
//

long GetCoercionLogSize();		// Implemented in generated code

class CCoercionLog
{
public:
	CCoercionLog()
	{
		m_lMaxSize = GetCoercionLogSize();
	}

	~CCoercionLog()
	{
	}

	template <typename T>
	static CString FormatCoercionRecord(CCallContext* pCallContext, T desiredVal, T coercedVal)
	{
		// From IVI 3.2:
		// A recommended format for the coercion record string is as follows:
		//		"Attribute " + <attribute name> +  [" on <repeated capability> " + <repeated capability identifier>] + " was coerced from " + <desiredVal> + " to " + <coercedVal>.
		// And example coercion record string is as follows:
		//		"Attribute TKTDS500_ATTR_VERTICAL_RANGE on channel ch1 was coerced from 9.0 to 10.0."
		
		CString strDesiredValue = FormatCoercionRecordValue(desiredVal);
		CString strCoercedValue = FormatCoercionRecordValue(coercedVal);

		CString strInterface = pCallContext->GetInterface();
		CString strProperty = pCallContext->GetMethod();
		strProperty.Delete(0, 4);	// delete "get_" or "put_" prefix

		CString strRepCapIdentifier = pCallContext->GetCallContextManager()->GetRepeatedCapabilityIdentifier();
		if (strRepCapIdentifier != _T(""))
		{
			strRepCapIdentifier += _T(".");
		}

		CString strRecord;
		strRecord.Format(_T("%s%s.%s coerced from %s to %s."), strRepCapIdentifier, strInterface, strProperty, strDesiredValue, strCoercedValue);

		return strRecord;
	}

	void AddCoercionRecord(const CString& strRecord)
	{
		if ((long)m_rgCoercionRecords.GetCount() == m_lMaxSize)
		{
			m_rgCoercionRecords.RemoveAt(0, 1);
		}

		m_rgCoercionRecords.Add(strRecord);
	}

	CString GetNextCoercionRecordAndRemove()
	{
		// Get the oldest coercion record and remove it from the collection
		CString strCoercionRecord;
		if (m_rgCoercionRecords.GetCount() > 0)
		{
			strCoercionRecord = m_rgCoercionRecords[0];
			m_rgCoercionRecords.RemoveAt(0, 1);
		}

		return strCoercionRecord;
	}

	long GetMaxSize() const { return m_lMaxSize; }
	void SetMaxSize(long lMaxSize) 
	{ 
		_SetMaxSize(lMaxSize);

		// If the new max size is smaller than the current size, then we need to trim
		long lExtraElements = (long)m_rgCoercionRecords.GetCount() - m_lMaxSize;
		if (lExtraElements > 0)
		{
			m_rgCoercionRecords.RemoveAt(0, lExtraElements);
		}
	}

private:
	static CString FormatCoercionRecordValue(double dVal)
	{
		CString strValue;
		strValue.Format(_T("%0.15g"), dVal);
		return strValue;
	}

	static CString FormatCoercionRecordValue(float fVal)
	{
		return FormatCoercionRecordValue((double)fVal);
	}

	static CString FormatCoercionRecordValue(long lVal)
	{
		CString strValue;
		strValue.Format(_T("%d"), lVal);
		return strValue;
	}

	static CString FormatCoercionRecordValue(int nVal)
	{
		return FormatCoercionRecordValue((long)nVal);
	}

	static CString FormatCoercionRecordValue(short sVal)
	{
		return FormatCoercionRecordValue((long)sVal);
	}

private:
	void _SetMaxSize(long lMaxSize)
	{
		// Max size must be at least 1
		m_lMaxSize = (lMaxSize > 1) ? lMaxSize : 1;
	}

private:
	CAtlArray<CString> m_rgCoercionRecords;
	long m_lMaxSize;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CCoercionLogManager
//

class CCoercionLogManager
{
public:
	CCoercionLogManager(CCallContextManager* pCallContextManager)
		: m_pCallContextManager(pCallContextManager)
	{
	}

	virtual CCoercionLog* GetCoercionLog() = 0;
	virtual bool GetRecordCoercions() = 0;

	template <typename T>
	void AddCoercionRecord(T desiredVal, T coercedVal)
	{
		CCallContext* pCallContext = m_pCallContextManager->GetCallContext();
		CString strRecord = CCoercionLog::FormatCoercionRecord<T>(pCallContext, desiredVal, coercedVal);
		AddCoercionRecord(strRecord);
	}

	virtual void AddCoercionRecord(const CString& strRecord)
	{
		GetCoercionLog()->AddCoercionRecord(strRecord);
	}

	CString GetNextCoercionRecordAndRemove()
	{
		return GetCoercionLog()->GetNextCoercionRecordAndRemove();
	}

private:
	CCallContextManager* m_pCallContextManager;
};

///////////////////////////////////////////////////////////////////////////////
//
//	CCoercionPolicy
//

template <typename T>
class CCoercionPolicy
	: public CModelBasedType
{
public:
	CCoercionPolicy(const CString& strModelOrFamily) 
		: CModelBasedType(strModelOrFamily)
	{}

	virtual T CoerceValue(T val) const = 0;
	virtual bool ValueInRange(T val) const = 0;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CSimpleCoercionPolicy
//

template <typename T>
class CSimpleCoercionPolicy
	: public CCoercionPolicy<T>
{
public:
	CSimpleCoercionPolicy(T min, T max, T coerce, const CString& strModelOrFamily = _T("")) 
		: m_min(min), m_max(max), m_coerce(coerce),  CCoercionPolicy<T>(strModelOrFamily)
	{}

	virtual T CoerceValue(T val) const
	{
		return ValueInRange(val) ? m_coerce : val;
	}

	virtual bool ValueInRange(T val) const
	{
		return (val >= m_min) && (val < m_max);
	}

protected:
	T m_min;
	T m_max;
	T m_coerce;
};


///////////////////////////////////////////////////////////////////////////////
//
//	CNullCoercionPolicy
//

template <typename T>
class CNullCoercionPolicy
	: public CCoercionPolicy<T>
{
public:
	CNullCoercionPolicy(const CString& strModelOrFamily = _T("")) 
		: CCoercionPolicy<T>(strModelOrFamily)
	{}

	virtual T CoerceValue(T val) const
	{
		return val;
	}

	virtual bool ValueInRange(T val) const
	{
		NTL_UNUSED(val);

		return false;
	}
};


///////////////////////////////////////////////////////////////////////////////
//
//	CCoercionPolicyManager
//

template <typename T>
class CCoercionPolicyManager
{
public:
	CCoercionPolicyManager(CCoercionPolicy<T>* pFirstCoercionPolicy, ...);

	T CoerceValue(T val, const CString& strModel, CCoercionLogManager* pCoercionLogManager) const;

private:
	typedef CModelBasedMultiMap< CCoercionPolicy<T> > CoercionPolicyMap;				// key = model, value = CCoercionPolicy
	CoercionPolicyMap m_mapCoercionPolicies;						
};

template <typename T>
CCoercionPolicyManager<T>::CCoercionPolicyManager(CCoercionPolicy<T>* pFirstCoercionPolicy, ...)
{
	va_list argPtr;
	va_start(argPtr, pFirstCoercionPolicy);

	m_mapCoercionPolicies.BuildMap(pFirstCoercionPolicy, argPtr);

	va_end(argPtr);
}

template <typename T>
T CCoercionPolicyManager<T>::CoerceValue(T val, const CString& strModelIn, CCoercionLogManager* pCoercionLogManager) const
{
	T coercedVal = val;
	CString strModel = strModelIn;
	POSITION pos = m_mapCoercionPolicies.FindFirstWithKey(strModel);
	if (pos == NULL)
	{
		// No model-specific policy provided, so use default policy
		strModel = _T("");
		pos = m_mapCoercionPolicies.FindFirstWithKey(strModel);
	}

	while (pos != NULL)
	{
		const CCoercionPolicy<T>* pCoercionPolicy = m_mapCoercionPolicies.GetNextValueWithKey(pos, strModel);
		if (pCoercionPolicy->ValueInRange(val))
		{
			coercedVal = pCoercionPolicy->CoerceValue(val);

			// If coercion recording is turned on, then record this coercion
			if (pCoercionLogManager->GetRecordCoercions())
			{
				pCoercionLogManager->AddCoercionRecord<T>(val, coercedVal);
			}

			break;
		}
	}

	return coercedVal;
}


#endif // __NTLCOERCION_INCLUDED
