/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2014 Raytrix GmbH. All rights reserved.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once

#include <map>
#include <vector>
#include <string>
#include <set>
#include <memory>
#include "Rx.Core/RxString.h"
#include "Rx.Core/RxExceptions.h"

#include "RxArrayUInt.h"
#include "RxArrayDouble.h"
#include "RxArrayString.h"
#include "RxParMapConstraints.h"
#include "EValueType.h"

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// namespace: Rx
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rx
{
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 	A value.
	/// </summary>
	///
	/// <typeparam name="TValue"> The data type of the value. </typeparam>
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TValue>
	struct SValue
	{
		/// <summary> The current value. </summary>
		TValue Current;
		/// <summary> The initial value. </summary>
		TValue Initial;
		/// <summary> The default value. </summary>
		TValue Default;
	};

	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 	A value definition.
	/// </summary>
	///
	/// <typeparam name="TGroup"> The data type of the group. </typeparam>
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TGroup>
	struct SValueDef
	{
		/// <summary> True if the value is readable. </summary>
		bool bRead;
		/// <summary> True if the value is writable. </summary>
		bool bWrite;
		/// <summary> True if the value is extern. </summary>
		bool bExtern;
		/// <summary> True if the value is exportable. </summary>
		bool bExportable;
		/// <summary> The name of the value. </summary>
		CRxString sxName;
		/// <summary> The GUID of the value. </summary>
		CRxString sxGUID;
		/// <summary> The type of the value. </summary>
		EValueType::ID eType;
		/// <summary> The group of the value. </summary>
		TGroup tGroup;
		/// <summary> The constraint. </summary>
		std::shared_ptr<Constraint::CAny> pxConstraint;
	};

	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 	A key value map class.
	///
	/// 	This class is able to map a key of a given data type to a value of one of six possible data types. A value is described
	/// 	by a value definition and has an initial value and a default value.
	/// </summary>
	///
	/// <typeparam name="TKey">   The data type of the key. </typeparam>
	/// <typeparam name="TGroup"> The data type of the group. </typeparam>
	/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	template<typename TKey, typename TGroup>
	class CKeyValueMap
	{
	public:

		typedef std::map < TKey, SValueDef < TGroup >> TMapDefs;

		/// <summary> Defines an alias representing the callback function type of parameter-set-events. </summary>
		typedef void (* TParamSetCallback)(const TKey& tKey, void* pvContext);

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Default constructor.
		/// </summary>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		CKeyValueMap() = default;

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Copy constructor.
		/// </summary>
		///
		/// <param name="xMap"> The map to copy. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		CKeyValueMap(const CKeyValueMap<TKey, TGroup>& xMap)
		{
			*this = xMap;
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Destructor.
		/// </summary>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		~CKeyValueMap() = default;

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Assignment operator.
		/// </summary>
		///
		/// <param name="xMap"> The map. </param>
		///
		/// <returns> A shallow copy of this object. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		CKeyValueMap<TKey, TGroup>& operator=(const CKeyValueMap<TKey, TGroup>& xMap)
		{
			m_xMapDefs        = xMap.m_xMapDefs;
			m_xMapUInt        = xMap.m_xMapUInt;
			m_xMapDouble      = xMap.m_xMapDouble;
			m_xMapString      = xMap.m_xMapString;
			m_xMapArrayUInt   = xMap.m_xMapArrayUInt;
			m_xMapArrayDouble = xMap.m_xMapArrayDouble;
			m_xMapArrayString = xMap.m_xMapArrayString;

			// Don't copy the callback
			return *this;
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Clears this object to its blank/initial state.
		/// </summary>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void Clear()
		{
			m_xMapDefs.clear();
			m_xMapUInt.clear();
			m_xMapDouble.clear();
			m_xMapString.clear();
			m_xMapArrayUInt.clear();
			m_xMapArrayDouble.clear();
			m_xMapArrayString.clear();
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the size of this map.
		/// </summary>
		///
		/// <returns> The number if values in this map. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		size_t Size() const
		{
			return m_xMapDefs.size();
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Query if this map contains the given key.
		/// </summary>
		///
		/// <param name="tKey"> The key to test for containment. </param>
		///
		/// <returns> True if the key is in this map, false if not. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool HasKey(const TKey& tKey) const
		{
			try
			{
				return m_xMapDefs.find(tKey) != m_xMapDefs.end();
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error testing for key. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error testing for key", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Get a list of all keys matching the given group.
		/// </summary>
		///
		/// <param name="tGroup"> Only return keys of this group. </param>
		///
		/// <returns> A list of keys associated with the given group. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		std::vector<TKey> FilterByGroup(const TGroup& tGroup) const
		{
			try
			{
				// Iterate over all properties and add all properties matching the given group
				std::vector<TKey> vecFilterIDs;
				for (const auto& kv : m_xMapDefs)
				{
					// Add all properties with the matching group and verify exclude constraint
					if (kv.second.tGroup == tGroup)
					{
						vecFilterIDs.push_back(kv.first);
					}
				}

				return vecFilterIDs;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error filtering by group. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error filtering by group", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets a set of all groups.
		/// </summary>
		///
		/// <returns> The set of groups. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		std::set<TGroup> GetGroups() const
		{
			try
			{
				std::set<TGroup> setGroups;
				for (const auto& kv : m_xMapDefs)
				{
					setGroups.insert(kv.second.tGroup);
				}

				return setGroups;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error getting groups. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error getting groups", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Query if the given key is in the given group.
		/// </summary>
		///
		/// <param name="tKey">   The key. </param>
		/// <param name="tGroup"> The group. </param>
		///
		/// <returns> True if key is in given group, false if not. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool IsKeyInGroup(const TKey& tKey, const TGroup& tGroup) const
		{
			return GetValueDef(tKey).tGroup == tGroup;
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Query if the value associated with the given key is readable. It must be flagged as readable and NOT extern.
		/// </summary>
		///
		/// <param name="tKey"> The key. </param>
		///
		/// <returns> True if readable, false if not. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool IsReadable(const TKey& tKey) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);
				return xDef.bRead && !xDef.bExtern;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error testing read flag. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error testing read flag", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Query if the value associated with the given key is writable. It must be flagged as writable and NOT extern.
		/// </summary>
		///
		/// <param name="tKey"> The key. </param>
		///
		/// <returns> True if writable, false if not. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		bool IsWritable(const TKey& tKey) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);
				return xDef.bWrite && !xDef.bExtern;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error testing write flag. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error testing write flag", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the key at the given index.
		/// </summary>
		///
		/// <param name="nIdx"> The index. </param>
		///
		/// <returns> The key. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const TKey& GetKey(size_t nIdx) const
		{
			try
			{
				if (nIdx >= m_xMapDefs.size())
				{
					RX_THROW(CRxException, "Index out of range");
				}

				// Increment iterator by nIdx and return the key
				return std::next(m_xMapDefs.begin(), nIdx)->first;
			}
			catch (std::exception& ex)
			{
				RX_THROW(CRxException, RX_S "Error getting key at given position. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error getting key at given position", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value definitions.
		/// </summary>
		///
		/// <returns> The value definitions. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const TMapDefs& GetValueDefs() const
		{
			return m_xMapDefs;
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value definition of the given key.
		/// </summary>
		///
		/// <param name="tKey"> The key. </param>
		///
		/// <returns> The value definition. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValueDef<TGroup>& GetValueDef(const TKey& tKey) const
		{
			try
			{
				return m_xMapDefs.at(tKey);
			}
			catch (std::exception&)
			{
				RX_THROW(CRxException, RX_S "Error getting value definition. The given key does not exist in this map");
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the string representation of the given value type.
		/// </summary>
		///
		/// <param name="eValueType"> The value type. </param>
		///
		/// <returns> The value type as a string. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		CRxString ToString(EValueType::ID eValueType) const
		{
			switch (eValueType)
			{
			case EValueType::UInt:        return "unsigned";
			case EValueType::Double:      return "double";
			case EValueType::String:      return "string";
			case EValueType::ArrayUInt:   return "unsigned[]";
			case EValueType::ArrayDouble: return "double[]";
			case EValueType::ArrayString: return "string[]";
			default: return "unknown";
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Set the parameter set callback function.
		/// </summary>
		///
		/// <param name="pxCallback"> The callback. </param>
		/// <param name="pvContext">  [in] A context pointer. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void SetParamSetCallback(TParamSetCallback pxCallback, void* pvContext)
		{
			m_pxCallback        = pxCallback;
			m_pvCallbackContext = pvContext;
		}

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Adds tKey.
		/// </summary>
		///
		/// <param name="tKey">		    The key. </param>
		/// <param name="tGroup">	    The group. </param>
		/// <param name="sxName">	    Name of the sx. </param>
		/// <param name="sxGUID">	    Unique identifier for the sx. </param>
		/// <param name="bRead">	    true if the data was read. </param>
		/// <param name="bWrite">	    true to write. </param>
		/// <param name="bExtern">	    true to extern. </param>
		/// <param name="bExportable">  true if exportable. </param>
		/// <param name="eType">	    The type. </param>
		/// <param name="pxConstraint"> (Optional) the constraint. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void Add(const TKey& tKey,
				const TGroup& tGroup,
				const CRxString& sxName,
				const CRxString& sxGUID,
				bool bRead,
				bool bWrite,
				bool bExtern,
				bool bExportable,
				EValueType::ID eType,
				Constraint::CAny* pxConstraint = nullptr)
		{
			try
			{
				if (HasKey(tKey))
				{
					RX_THROW(CRxException, RX_S "The key already exists");
				}

				/************************************************************************/
				/* DEBUG CHECKS                                                         */
				/************************************************************************/
				#ifndef RX_RTM

					// Check for duplicated GUID if one is given
					if (sxGUID != "")
					{
						for (const auto& kv : m_xMapDefs)
						{
							if (kv.second.sxGUID == sxGUID)
							{
								RX_THROW(CRxException, RX_S "DEBUG: Duplicate parameter GUID found. GUID is '" << kv.second.sxGUID << "'");
							}
						}
					}

				#endif

				/************************************************************************/
				/* ADD                                                                  */
				/************************************************************************/
				SValueDef<TGroup>& xDef = m_xMapDefs[tKey];

				// Configure this definition
				xDef.bRead       = bRead;
				xDef.bWrite      = bWrite;
				xDef.bExtern     = bExtern;
				xDef.bExportable = bExportable;
				xDef.eType       = eType;
				xDef.tGroup      = tGroup;
				xDef.sxName      = sxName;
				xDef.sxGUID      = sxGUID;
				xDef.pxConstraint.reset(pxConstraint);

				// Add value entry
				if (!bExtern)
				{
					switch (eType)
					{
					case EValueType::UInt:
						m_xMapUInt[tKey];
						break;
					case EValueType::Double:
						m_xMapDouble[tKey];
						break;
					case EValueType::String:
						m_xMapString[tKey];
						break;
					case EValueType::ArrayUInt:
						m_xMapArrayUInt[tKey];
						break;
					case EValueType::ArrayDouble:
						m_xMapArrayDouble[tKey];
						break;
					case EValueType::ArrayString:
						m_xMapArrayString[tKey];
						break;
					default:
						break;
					}
				}
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error adding key '" << sxName << "'. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error adding key '" << sxName << "'", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		void SetAll(const TKey& tKey, const TValue& tDefault)
		{
			try
			{
				SValue<TValue>& xValue = _GetValue(tKey, TValue());
				xValue.Current = tDefault;
				xValue.Initial = tDefault;
				xValue.Default = tDefault;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error setting all values. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error setting all values", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		void SetDefault(const TKey& tKey, const TValue& tDefault)
		{
			try
			{
				_GetValue(tKey, TValue()).Default = tDefault;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error setting default value. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error setting default value", ex);
			}
		}

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		inline void Set(const TKey& tKey, const TValue& tValue)
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);

				EValueType::ID eType = GetValueType<TValue>();
				if (xDef.eType != eType)
				{
					RX_THROW(CRxException, RX_S "The parameter is of type '" << ToString(xDef.eType) << "' and you've provided '" << ToString(eType) << "'");
				}

				if (xDef.pxConstraint)
				{
					if (!xDef.pxConstraint->IsValid(&tValue))
					{
						RX_THROW(CRxException, RX_S "Invalid value: '" << xDef.sxName << "': " << xDef.pxConstraint->GetInfoString(&tValue));
					}
				}

				// If the value is extern, then it does not exist in this map
				if (xDef.bExtern)
				{
					RX_THROW(CRxException, "The value associated with the given key is stored externally");
				}

				_GetValue(tKey, TValue()).Current = tValue;

				// Fire the parameter-set-event
				if (m_pxCallback)
				{
					m_pxCallback(tKey, m_pvCallbackContext);
				}
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error setting value. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error setting value", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		inline const TValue& Get(const TKey& tKey) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);

				EValueType::ID eType = GetValueType<TValue>();
				if (xDef.eType != eType)
				{
					RX_THROW(CRxException, RX_S "The parameter is of type '" << ToString(xDef.eType) << "' and you've requested '" << ToString(eType) << "'");
				}

				// If the value is extern, then it does not exist in this map
				if (xDef.bExtern)
				{
					RX_THROW(CRxException, "The value associated with the given key is stored externally");
				}

				return _GetValue(tKey, TValue()).Current;
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error getting value. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error getting value", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		inline bool GetAsBool(const TKey& tKey) const
		{
			try
			{
				if (GetValueDef(tKey).eType != EValueType::UInt)
				{
					RX_THROW(CRxException, RX_S "The parameter is not of type '" << ToString(EValueType::UInt) << "'");
				}

				return Get<unsigned>(tKey) != 0U;
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error getting value", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		inline int GetAsInt(const TKey& tKey) const
		{
			try
			{
				if (GetValueDef(tKey).eType != EValueType::UInt)
				{
					RX_THROW(CRxException, RX_S "The parameter is not of type '" << ToString(EValueType::UInt) << "'");
				}

				return (int) Get<unsigned>(tKey);
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error getting value", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		inline float GetAsFloat(const TKey& tKey) const
		{
			try
			{
				if (GetValueDef(tKey).eType != EValueType::Double)
				{
					RX_THROW(CRxException, RX_S "The parameter is not of type '" << ToString(EValueType::Double) << "'");
				}

				return (float) Get<double>(tKey);
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, RX_S "Error getting value", ex);
			}
		}

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Query if the given value is valid for the given key. This tests the data type and possible constraints.
		/// </summary>
		///
		/// <typeparam name="TValue"> The data type of the value. </typeparam>
		/// <param name="tKey">   The key. </param>
		/// <param name="tValue"> The value. </param>
		///
		/// <returns> True if valid, false if not. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		bool IsValid(const TKey& tKey, const TValue& tValue) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);
				if (xDef.eType != GetValueType<TValue>())
				{
					return false;
				}

				// No constraint given or constraint valid
				return !xDef.pxConstraint || xDef.pxConstraint->IsValid(&tValue);
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error testing for valid key value. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error testing for valid key value", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value range of the value associated with the given key.
		/// </summary>
		///
		/// <typeparam name="TValue"> The data type of the value. </typeparam>
		/// <param name="tKey"> The key. </param>
		/// <param name="tMin"> [out] The minimum. </param>
		/// <param name="tMax"> [out] The maximum. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		void GetValueRange(const TKey& tKey, TValue& tMin, TValue& tMax) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);
				if (!xDef.bRead)
				{
					RX_THROW(CRxException, "The value associated with the given key isn't readable");
				}

				EValueType::ID eParTypeID = GetValueType<TValue>();
				if (xDef.eType != eParTypeID)
				{
					RX_THROW(CRxException, RX_S "Expected type '" << ToString(xDef.eType) << "' differs from '" << ToString(eParTypeID) << "'");
				}

				if (!xDef.pxConstraint)
				{
					RX_THROW(CRxException, "Value contraint is invalid");
				}

				Constraint::CRange<TValue>* pRange = dynamic_cast<Constraint::CRange<TValue>*>(xDef.pxConstraint.get());

				if (!pRange)
				{
					RX_THROW(CRxException, "Value contraint is not a valid range constraint");
				}

				pRange->Get(tMin, tMax);
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error getting value range. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error getting value range", ex);
			}
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value range of the value associated with the given key.
		/// </summary>
		///
		/// <typeparam name="TArray"> The array type. </typeparam>
		/// <typeparam name="TValue"> The data type of the value. </typeparam>
		/// <param name="tKey">		    The key. </param>
		/// <param name="nArrayLenMin"> [out] The array length minimum. </param>
		/// <param name="nArrayLenMax"> [out] The array length maximum. </param>
		/// <param name="tMin">		    [out] The minimum. </param>
		/// <param name="tMax">		    [out] The maximum. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TArray, class TValue>
		void GetValueRange(const TKey& tKey, size_t& nArrayLenMin, size_t& nArrayLenMax, TValue& tMin, TValue& tMax) const
		{
			try
			{
				const SValueDef<TGroup>& xDef = GetValueDef(tKey);
				if (!xDef.bRead)
				{
					RX_THROW(CRxException, "The value associated with the given key isn't readable");
				}

				EValueType::ID eParTypeID = GetValueType<TArray>();
				if (xDef.eType != eParTypeID)
				{
					RX_THROW(CRxException, RX_S "Expected type '" << ToString(xDef.eType) << "' differs from '" << ToString(eParTypeID) << "'");
				}

				if (!xDef.pxConstraint)
				{
					RX_THROW(CRxException, "Value contraint is invalid");
				}

				Constraint::CArrayRange<TArray, TValue>* pRange = dynamic_cast<Constraint::CArrayRange<TArray, TValue>*>(xDef.pxConstraint.get());

				if (!pRange)
				{
					RX_THROW(CRxException, "Value contraint is not a valid range constraint");
				}

				pRange->Get(nArrayLenMin, nArrayLenMax, tMin, tMax);
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error getting value range. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error getting value range", ex);
			}
		}

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Sets the value associated with the given key to its initial or its default value.
		/// </summary>
		///
		/// <param name="tKey">			   The key. </param>
		/// <param name="bResetToInitial"> (Optional) True to reset to initial, false to reset to default. </param>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void ResetValue(const TKey& tKey, bool bResetToInitial = false)
		{
			try
			{
				switch (GetValueDef(tKey).eType)
				{
				case EValueType::UInt:
					m_xMapUInt.at(tKey).Current = (bResetToInitial) ? m_xMapUInt.at(tKey).Initial : m_xMapUInt.at(tKey).Default;
					break;
				case EValueType::Double:
					m_xMapDouble.at(tKey).Current = (bResetToInitial) ? m_xMapDouble.at(tKey).Initial : m_xMapDouble.at(tKey).Default;
					break;
				case EValueType::String:
					m_xMapString.at(tKey).Current = (bResetToInitial) ? m_xMapString.at(tKey).Initial : m_xMapString.at(tKey).Default;
					break;
				case EValueType::ArrayUInt:
					m_xMapArrayUInt.at(tKey).Current = (bResetToInitial) ? m_xMapArrayUInt.at(tKey).Initial : m_xMapArrayUInt.at(tKey).Default;
					break;
				case EValueType::ArrayDouble:
					m_xMapArrayDouble.at(tKey).Current = (bResetToInitial) ? m_xMapArrayDouble.at(tKey).Initial : m_xMapArrayDouble.at(tKey).Default;
					break;
				case EValueType::ArrayString:
					m_xMapArrayString.at(tKey).Current = (bResetToInitial) ? m_xMapArrayString.at(tKey).Initial : m_xMapArrayString.at(tKey).Default;
					break;
				default:
					break;
				}

				// Fire the parameter-set-event
				if (m_pxCallback)
				{
					m_pxCallback(tKey, m_pvCallbackContext);
				}
			}
			catch (std::exception& ex)
			{
				RX_THROW(Rx::CRxException, RX_S "Error resetting value. " << ex.what());
			}
			catch (Rx::CRxException& ex)
			{
				RX_RETHROW(Rx::CRxException, "Error resetting value", ex);
			}
		}

	public:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		template<class TValue>
		static EValueType::ID GetValueType()
		{
			if (std::is_same<TValue, unsigned>::value)
			{
				return EValueType::UInt;
			}

			if (std::is_same<TValue, double>::value)
			{
				return EValueType::Double;
			}

			if (std::is_same<TValue, CRxString>::value)
			{
				return EValueType::String;
			}

			if (std::is_same<TValue, CRxArrayUInt>::value)
			{
				return EValueType::ArrayUInt;
			}

			if (std::is_same<TValue, CRxArrayDouble>::value)
			{
				return EValueType::ArrayDouble;
			}

			if (std::is_same<TValue, CRxArrayString>::value)
			{
				return EValueType::ArrayString;
			}
		}

	private:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value struct.
		///     The anonymous second parameter is used to allow the function overloading.
		/// </summary>
		///
		/// <typeparam name="TValue"> VALUE. </typeparam>
		/// <param name="tKey"> The key. </param>
		///
		/// <returns> The value. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<unsigned int>& _GetValue(const TKey& tKey, unsigned int)
		{
			return m_xMapUInt.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<double>& _GetValue(const TKey& tKey, double)
		{
			return m_xMapDouble.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<CRxString>& _GetValue(const TKey& tKey, CRxString)
		{
			return m_xMapString.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<CRxArrayUInt>& _GetValue(const TKey& tKey, CRxArrayUInt)
		{
			return m_xMapArrayUInt.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<CRxArrayDouble>& _GetValue(const TKey& tKey, CRxArrayDouble)
		{
			return m_xMapArrayDouble.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		SValue<CRxArrayString>& _GetValue(const TKey& tKey, CRxArrayString)
		{
			return m_xMapArrayString.at(tKey);
		}

	private:

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 	Gets the value struct.
		///     The anonymous second parameter is used to allow the function overloading.
		/// </summary>
		///
		/// <typeparam name="TValue"> VALUE. </typeparam>
		/// <param name="tKey"> The key. </param>
		///
		/// <returns> The value. </returns>
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<unsigned int>& _GetValue(const TKey& tKey, unsigned int) const
		{
			return m_xMapUInt.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<double>& _GetValue(const TKey& tKey, double) const
		{
			return m_xMapDouble.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<CRxString>& _GetValue(const TKey& tKey, CRxString) const
		{
			return m_xMapString.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<CRxArrayUInt>& _GetValue(const TKey& tKey, CRxArrayUInt) const
		{
			return m_xMapArrayUInt.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<CRxArrayDouble>& _GetValue(const TKey& tKey, CRxArrayDouble) const
		{
			return m_xMapArrayDouble.at(tKey);
		}

		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		const SValue<CRxArrayString>& _GetValue(const TKey& tKey, CRxArrayString) const
		{
			return m_xMapArrayString.at(tKey);
		}

	public:

		TMapDefs m_xMapDefs;

		std::map < TKey, SValue < unsigned >> m_xMapUInt;
		std::map < TKey, SValue < double >> m_xMapDouble;
		std::map < TKey, SValue < CRxString >> m_xMapString;

		std::map < TKey, SValue < CRxArrayUInt >> m_xMapArrayUInt;
		std::map < TKey, SValue < CRxArrayDouble >> m_xMapArrayDouble;
		std::map < TKey, SValue < CRxArrayString >> m_xMapArrayString;

		TParamSetCallback m_pxCallback = nullptr;
		void* m_pvCallbackContext      = nullptr;
	};
}
