/***************************************************************************
*                                                                          *
*  Copyright (C) Golaem S.A.  All Rights Reserved.                         *
*                                                                          *
****************************************************************************/

#pragma once

#include "glmAssert.h"

namespace glm
{
	class RTTI;
	class BinaryStream;
	class XMLStream;
	class DataItem;

	// base class for inspectable attributes
	class GLM_CORE_API AbstractAttribute
	{
		friend class BinaryStream;
		friend class XMLStream;

	public:

		struct AttributeType
		{
		public:

			enum Value
			{
				VALUE = 0,
				POINTER,
				CONST_POINTER,
				REFERENCE,
				CONST_REFERENCE,
				END
			};
		};

		virtual ~AbstractAttribute();

		const char* getName() const;

		/**
		* \brief get the value of the inspectable attribute
		*
		* \param owner reference to the class instance from which the attribute value must be retrieved
		* \note Assert [DEBUG only] that the attribute is of type TValue
		* \return the attribute value
		*/
		template <class TValue, class TOwner> TValue getValue(const TOwner& owner) const;

		/**
		* \brief get the pointer to the inspectable attribute
		*
		* Should be inherited with TValue = P* for instanciation issues on TValue for abstract items
		*
		* \param owner reference to the class instance from which the attribute value must be retrieved
		* \note [DEBUG only] assert that the attribute is not of type TValue
		* \return the attribute value
		*/
		template <class TValue, class TOwner> TValue* getPointer(const TOwner* owner) const;

		/**
		* \brief get the const pointer to the inspectable attribute
		*
		* Should be inherited with TValue = P* for instanciation issues on TValue for abstract items
		*
		* \param owner reference to the class instance from which the attribute value must be retrieved
		* \note [DEBUG only] assert that the attribute is not of type TValue
		* \return the attribute value
		*/
		template <class TValue, class TOwner> const TValue* getConstPointer(const TOwner& owner) const;

		/**
		* \brief get the reference to the inspectable attribute
		*
		* \param owner reference to the class instance from which the attribute value must be retrieved
		* \note [DEBUG only] assert that the attribute is not of type TValue
		* \return the attribute value
		*/
		template <class TValue, class TOwner> TValue& getReference(const TOwner& owner) const;

		/**
		* \brief get the const reference to the inspectable attribute
		*
		* \param owner reference to the class instance from which the attribute value must be retrieved
		* \note [DEBUG only] assert that the attribute is not of type TValue
		* \return the attribute value
		*/
		template <class TValue, class TOwner> const TValue& getConstReference(const TOwner& owner) const;

		/**
		* \brief set accessor to the value of the inspectable attribute
		*
		* It is an error to call this method if the attribute is read only, which
		* leads to an assertion.
		*
		* \param owner reference to the class instance from which the attribute value must be setted
		* \note assert that instance corresponds to a read-only attribute.
		* \param value to assign to the attribute
		*/
		template <class TValue, class TOwner> void setValue(TOwner& owner, TValue value) const;

		/**
		* \brief set accessor to the pointer of the inspectable attribute
		*
		* It is an error to call this method if the attribute is read only, which
		* leads to an assertion.
		*
		* Should be inherited with TValue = P* for instanciation issues on TValue for abstract items
		*
		* \param owner reference to the class instance from which the attribute value must be setted
		* \note assert that instance corresponds to a read-only attribute.
		* \param value a pointer to the value to assign to the attribute
		*/
		template <class TValue, class TOwner> void setPointer(TOwner* owner, TValue* value) const;

		/**
		* \brief set accessor to the const pointer of the inspectable attribute
		*
		* It is an error to call this method if the attribute is read only, which
		* leads to an assertion.
		*
		* Should be inherited with TValue = P* for instanciation issues on TValue for abstract items
		*
		* \param owner reference to the class instance from which the attribute value must be setted
		* \note assert that instance corresponds to a read-only attribute.
		* \param value a pointer to the value to assign to the attribute
		*/
		template <class TValue, class TOwner> void setConstPointer(TOwner& owner, const TValue* value) const;

		/**
		* \brief set accessor to the reference of the inspectable attribute
		*
		* It is an error to call this method if the attribute is read only, which
		* leads to an assertion.
		*
		* \param owner reference to the class instance from which the attribute value must be setted
		* \note assert that instance corresponds to a read-only attribute.
		* \param value a reference to the value to assign to the attribute
		*/
		template <class TValue, class TOwner> void setReference(TOwner& owner, TValue& value) const;

		/**
		* \brief set accessor to the const reference of the inspectable attribute
		*
		* It is an error to call this method if the attribute is read only, which
		* leads to an assertion.
		*
		* \param owner reference to the class instance from which the attribute value must be setted
		* \note assert that instance corresponds to a read-only attribute.
		* \param value a const reference to the value to assign to the attribute
		*/
		template <class TValue, class TOwner> void setConstReference(TOwner& owner, const TValue& value) const;

		AttributeType::Value getAttributeType() const; // enum indicating if the type is a value, a pointer or a reference
		virtual const RTTI& getAttributeRTTI() const = 0; // returns RTTI info associated with the associated attribute
		virtual const RTTI& getOwnerRTTI() const; // returns RTTI info associated with the associated attribute owner

	protected:

		AbstractAttribute() {}
		AbstractAttribute(const char* name, const RTTI* ownerRTTI);
		AbstractAttribute(const AbstractAttribute& attribute);
		AbstractAttribute& operator=(const AbstractAttribute& attribute);

		AttributeType::Value _attributeType; // is the attribute a value, a pointer, a reference?

	private:

		virtual void pack(BinaryStream&, const void*) const = 0;
		virtual void unpack(BinaryStream&, void*) const = 0;
		virtual void pack(XMLStream&, DataItem&, const void*) const = 0;
		virtual void unpack(XMLStream&, const DataItem&, void*) const = 0;

		const char* _name; 
		const RTTI* _ownerRTTI; // attribute owner RTTI
	};

	//-------------------------------------------------------------------------
	inline const char* AbstractAttribute::getName() const
	{
		return _name;
	}
}

#include "glmAbstractAttributeT.h"

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
TValue glm::AbstractAttribute::getValue(const TOwner& owner) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::VALUE,
		"The attribute %s of type %s from an object of type %s is not a value",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI()),
		"unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	return static_cast<const AbstractAttributeT<TValue, TOwner>*>(this)->getValue(owner);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
TValue* glm::AbstractAttribute::getPointer(const TOwner* owner) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::POINTER,
		"The attribute %s of type %s from an object of type %s is not a pointer",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue*>::getClassRTTI()),
		"unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	return static_cast<const AbstractAttributeT<TValue*, TOwner>*>(this)->getPointer(owner);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
const TValue* glm::AbstractAttribute::getConstPointer(const TOwner& owner) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::CONST_POINTER,
		"The attribute %s of type %s from an object of type %s is not a const pointer",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<const TValue*>::getClassRTTI()),
		"unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	return static_cast<const AbstractAttributeT<const TValue*, TOwner>*>(this)->getConstPointer(owner);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
TValue& glm::AbstractAttribute::getReference(const TOwner& owner) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::REFERENCE,
		"The attribute %s of type %s from an object of type %s is not a reference",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue&>::getClassRTTI()),
		"unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	return static_cast<const AbstractAttributeT<TValue&, TOwner>*>(this)->getReference(owner);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
const TValue& glm::AbstractAttribute::getConstReference(const TOwner& owner) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::CONST_REFERENCE,
		"The attribute %s of type %s from an object of type %s is not a const reference",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<const TValue&>::getClassRTTI()),
		"unable to get a value of type %s from the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	return static_cast<const AbstractAttributeT<const TValue&, TOwner>*>(this)->getConstReference(owner);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
void glm::AbstractAttribute::setValue(TOwner& owner, TValue value) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::VALUE,
		"The attribute %s of type %s from an object of type %s is not a value",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI()),
		"unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	static_cast<const AbstractAttributeT<TValue, TOwner>*>(this)->setValue(owner, value);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
void glm::AbstractAttribute::setPointer(TOwner* owner, TValue* value) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::POINTER,
		"The attribute %s of type %s from an object of type %s is not a pointer",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue*>::getClassRTTI()),
		"unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	static_cast<const AbstractAttributeT<TValue*, TOwner>*>(this)->setPointer(owner, value);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
void glm::AbstractAttribute::setConstPointer(TOwner& owner, const TValue* value) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::CONST_POINTER,
		"The attribute %s of type %s from an object of type %s is not a const pointer",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<const TValue*>::getClassRTTI()),
		"unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	static_cast<const AbstractAttributeT<const TValue*, TOwner>*>(this)->setConstPointer(owner, value);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
void glm::AbstractAttribute::setReference(TOwner& owner, TValue& value) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::REFERENCE,
		"The attribute %s of type %s from an object of type %s is not a reference",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue&>::getClassRTTI()),
		"unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	static_cast<const AbstractAttributeT<TValue&, TOwner>*>(this)->setReference(owner, value);
}

//-------------------------------------------------------------------------
template <class TValue, class TOwner>
void glm::AbstractAttribute::setConstReference(TOwner& owner, const TValue& value) const
{
	GLM_DEBUG_ASSERT_MSG(_attributeType == AttributeType::CONST_REFERENCE,
		"The attribute %s of type %s from an object of type %s is not a const reference",
		getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	GLM_DEBUG_ASSERT_MSG(getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<TValue>::getClassRTTI())
		|| getAttributeRTTI().isDerivedFrom(glm::RTTIAccessor<const TValue&>::getClassRTTI()),
		"unable to set a value of type %s to the attribute named %s of type %s from an object of type %s",
		glm::RTTIAccessor<TValue>::getClassRTTI().getTypeName(), getName(), getAttributeRTTI().getTypeName(), getOwnerRTTI().getTypeName());

	static_cast<const AbstractAttributeT<const TValue&, TOwner>*>(this)->setConstReference(owner, value);
}

//-------------------------------------------------------------------------
inline glm::AbstractAttribute::AttributeType::Value glm::AbstractAttribute::getAttributeType() const
{
	return _attributeType;
}
