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

#include <sstream>
#include "glmDeque.h"
#include <set>
#include <map>
#include "glmList.h"
#include "glmQueue.h"
#include <utility>
#include "glmUuid.h"
#include "glmMemory.h"
#include "glmRTTI.h"
#include "glmSet.h"
#include "glmMap.h"
#include "glmAssert.h"

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_ATTRIBUTE( Attribute ) \
	void read( Attribute& value ) { doReadAttribute( value ) ; } \
	void write( Attribute value ) { doWriteAttribute( value ) ;	} \
	BIN_SERIALIZE_POINTER( Attribute )

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_POINTER( Pointer ) \
	void read( Pointer*& value ) { doRead( value ) ; } \
	void write( Pointer* value ) { doWrite( value ) ; } \
	void read( const Pointer*& value ) { doRead( value ) ; } \
	void write( const Pointer* value ) { doWrite( value ) ; }

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_POINTER_T( Pointer ) \
	template <class T> void read( Pointer<T>*& value ) { doRead( value ) ; } \
	template <class T> void write( Pointer<T>* value ) { doWrite( value ) ; } \
	template <class T> void read( const Pointer<T>*& value ) { doRead( value ) ; } \
	template <class T> void write( const Pointer<T>* value ) { doWrite( value ) ; }

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_POINTER_T0_T1( Pointer ) \
	template <class T0, class T1> void read( Pointer<T0, T1>*& value ) { doRead( value ) ; } \
	template <class T0, class T1> void write( Pointer<T0, T1>* value ) { doWrite( value ) ; } \
	template <class T0, class T1> void read( const Pointer<T0, T1>*& value ) { doRead( value ) ; } \
	template <class T0, class T1> void write( const Pointer<T0, T1>* value ) { doWrite( value ) ; }

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_POINTER_T0_T1_T2( Pointer ) \
	template <class T0, class T1, class T2> void read( Pointer<T0, T1, T2>*& value ) { doRead( value ) ; } \
	template <class T0, class T1, class T2> void write( Pointer<T0, T1, T2>* value ) { doWrite( value ) ; } \
	template <class T0, class T1, class T2> void read( const Pointer<T0, T1, T2>*& value ) { doRead( value ) ; } \
	template <class T0, class T1, class T2> void write( const Pointer<T0, T1, T2>* value ) { doWrite( value ) ; }

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_POINTER_T0_T1_T2_T3( Pointer ) \
	template <class T0, class T1, class T2, class T3> void read( Pointer<T0, T1, T2, T3>*& value ) { doRead( value ) ; } \
	template <class T0, class T1, class T2, class T3> void write( Pointer<T0, T1, T2, T3>* value ) { doWrite( value ) ; } \
	template <class T0, class T1, class T2, class T3> void read( const Pointer<T0, T1, T2, T3>*& value ) { doRead( value ) ; } \
	template <class T0, class T1, class T2, class T3> void write( const Pointer<T0, T1, T2, T3>* value ) { doWrite( value ) ; }

//-------------------------------------------------------------------------
#define BIN_SERIALIZE_CONTAINER( Container ) \
	template <class T> \
	void read( Container<T>& value ) \
	{ \
		unsigned int size( 0 ) ; \
		read( size ) ; \
		if ( _firstPass ) \
		{ \
			value.clear() ; \
			value.resize(size) ; \
		} \
		for ( unsigned int i( 0 ) ; i < size ; ++i ) \
		{ \
			read( value[i] ) ; \
		} \
	} \
	\
	template <class T> \
	void write( const Container<T>& value ) \
		{ \
		write( static_cast<unsigned int>( value.size() ) ) ; \
		typename Container<T>::const_iterator ite( value.begin() ) ; \
		for	( ; ite != value.end() ; ++ite ) \
		{ \
			write( *ite ) ; \
		} \
	} \
	BIN_SERIALIZE_POINTER_T( Container )


namespace glm
{
	class Vector2;
	class Vector3;
	class Vector4;
	template <class T> class Array;
	template <class T> class PODArray;
	template <class T> class PODArrayOfArray;
	template <class T> class SmartPointer;
	class ReferenceCounter;
	class Quaternion;
	class GlmString;

	// BinaryStream aggregates a stringstream and read/write the result of classes serialization as bytes.
	class GLM_CORE_API BinaryStream
	{
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared);

		BinaryStream();
		~BinaryStream();

		template <class Type> void unpack(Type& inspectableObject); // unpack the stream at its current position into inspectableObject 
		template <class Type> void pack(const Type& inspectableObject); // pack inspectableObject in the stream at its current position

																		//template <class Type> void unpack(glm::PODArray< IInspectable* >& inspectableObjects);
																		//void pack(const glm::PODArray< const IInspectable* >& inspectableObjects);

		std::stringstream& getStream();
		long getLength(); // return stream length
		long getPosition(); // return current position in the stream
		void exportTo(const char* fileName); // export the BinaryStream content to a binary file
		void importFrom(const char* fileName); // import the BinaryStream content from a binary file
		void importFrom(const uint8_t* buffer, const size_t bufferSize);  // import the BinaryStream content from a buffer
		bool isFirstPass();
		void setFirstPass(bool firstPass); // the pack and unpack are two-pass algorithms, this method is used for testing and debugging
		void pushValueInFifo(void*);
		void* popValuefromFifo();

		BIN_SERIALIZE_ATTRIBUTE(bool);
		BIN_SERIALIZE_ATTRIBUTE(short);
		BIN_SERIALIZE_ATTRIBUTE(unsigned short);
		BIN_SERIALIZE_ATTRIBUTE(int);
		BIN_SERIALIZE_ATTRIBUTE(unsigned int);
		BIN_SERIALIZE_ATTRIBUTE(long);
		BIN_SERIALIZE_ATTRIBUTE(uint64_t);
		BIN_SERIALIZE_ATTRIBUTE(float);
		BIN_SERIALIZE_ATTRIBUTE(double);
		BIN_SERIALIZE_ATTRIBUTE(char);
		BIN_SERIALIZE_ATTRIBUTE(Uuid);

		BIN_SERIALIZE_CONTAINER(glm::PODArray);
		BIN_SERIALIZE_CONTAINER(glm::Array);
		BIN_SERIALIZE_CONTAINER(glm::GlmDeque);
		BIN_SERIALIZE_POINTER_T(glm::GlmList);

		void read(char* value, unsigned int size);
		void write(const char* value, unsigned int size);

		void read(glm::GlmString& value);
		void write(const glm::GlmString& value);
		BIN_SERIALIZE_POINTER(glm::GlmString);

		template <class T>
		void read(glm::PODArrayOfArray<T>& value);
		template <class T>
		void write(const glm::PODArrayOfArray<T>& value);
		BIN_SERIALIZE_POINTER_T(glm::PODArrayOfArray);

		template<class FirstType, class SecondType>	void read(std::pair<FirstType, SecondType> & value);
		template<class FirstType, class SecondType>	void write(const std::pair<FirstType, SecondType> & value);
		BIN_SERIALIZE_POINTER_T0_T1(std::pair);

		// --- vector
		void read(Vector2& value);
		void read(Vector3& value);
		void read(Vector4& value);
		void read(Quaternion& value);

		void write(const Vector2& value);
		void write(const Vector3& value);
		void write(const Vector4& value);
		void write(const Quaternion& value);

		void read(Vector2*& value);
		void read(Vector3*& value);
		void read(Vector4*& value);
		void read(Quaternion*& value);

		void write(Vector2* value);
		void write(Vector3* value);
		void write(Vector4* value);
		void write(Quaternion* value);

		void read(const Vector2*& value);
		void read(const Vector3*& value);
		void read(const Vector4*& value);
		void read(const Quaternion*& value);

		void write(const Vector2* value);
		void write(const Vector3* value);
		void write(const Vector4* value);
		void write(const Quaternion* value);

		template <class Type> void read(glm::GlmList<Type> & value);
		template <class Type> void write(const glm::GlmList<Type>& value);

		template <class Type, class Comparator>	void read(std::set<Type, Comparator> & value);
		template <class Type, class Comparator>	void write(const std::set<Type, Comparator>& value);
		BIN_SERIALIZE_POINTER_T0_T1(std::set);

		template <class KeyType, class Type, class Comparator> void read(std::map<KeyType, Type, Comparator> & value);
		template <class KeyType, class Type, class Comparator> void write(const std::map<KeyType, Type, Comparator>& value);
		BIN_SERIALIZE_POINTER_T0_T1_T2(std::map);

		template <class Type>	void read(glm::GlmSet<Type> & value);
		template <class Type>	void write(const glm::GlmSet<Type>& value);
		BIN_SERIALIZE_POINTER_T(glm::GlmSet);

		template <class KeyType, class Type> void read(glm::GlmMap<KeyType, Type> & value);
		template <class KeyType, class Type> void write(const glm::GlmMap<KeyType, Type>& value);
		BIN_SERIALIZE_POINTER_T0_T1(glm::GlmMap);

		template <class TPointeeType> void read(SmartPointer<TPointeeType>& value);
		template <class TPointeeType> void write(const SmartPointer<TPointeeType>& value);

		template <class TPointeeType> void read(TPointeeType*& value);
		template <class TPointeeType> void write(TPointeeType* value);

		template <class TPointeeType> void read(const TPointeeType*& value);
		template <class TPointeeType> void write(const TPointeeType* value);

		template <class Type> void read(Type& value);
		template <class Type> void write(const Type& value, bool isValue = true);

		void clear(); // clear the maps, set the firstPass flag to true

	private:

		GLM_DISALLOW_COPY_AND_ASSIGN(BinaryStream);

		template <class TValue > void doReadAttribute(TValue& value);
		template <class TValue > void doWriteAttribute(TValue value);

		template <class TPointeeType> void doRead(TPointeeType*& value);
		template <class TPointeeType> void doWrite(TPointeeType* value);

		template <class TPointeeType> void doRead(const TPointeeType*& value);
		template <class TPointeeType> void doWrite(const TPointeeType* value);

		glm::GlmMap<const void*, unsigned int> _pointerToIdMap;
		glm::GlmMap<unsigned int, void*> _idToPointerMap;
		//std::set<const void*> _inspectablesPointersSet;
		struct TypedPointer
		{
			TypedPointer(const void* pointer, const TypeID& typeID) : _pointer(pointer), _typeID(typeID) {}
			bool operator < (const TypedPointer& other) const { return (_pointer < other._pointer) || ((_pointer == other._pointer) && (_typeID < other._typeID)); }
			bool operator == (const TypedPointer& other) const { return (_pointer == other._pointer) && (_typeID == other._typeID); }
			const void* _pointer;
			TypeID _typeID;
		};
		glm::Array<TypedPointer> _pointersValueToWrite;

#ifdef _MSC_VER
#	pragma warning( push )
#	pragma warning( disable : 4251)
#endif
		std::stringstream _binaryStream;

#ifdef _MSC_VER
#	pragma warning( pop )
#endif

		bool _firstPass;
		glm::GlmQueue< void* > _queueOfAccessorAttributeValues; //!< used between two passes to keep containers allocated instead of reallocating everything.
	};
}

#include "glmVector2.h"
#include "glmVector3.h"
#include "glmVector4.h"
#include "glmAngle.h"
#include "glmSmartPointer.h"
#include "glmUuid.h"
#include "glmArray.h"
#include "glmPODArray.h"
#include "glmPODArrayOfArray.h"

namespace glm
{
	//-------------------------------------------------------------------------
	template <class Type>
	void BinaryStream::unpack(Type& inspectableObject)
	{
		std::stringstream::pos_type currentStartPosition = _binaryStream.tellg();
		read(inspectableObject);
		_firstPass = false;
		_binaryStream.seekg(currentStartPosition);
		read(inspectableObject);
		_firstPass = true;
	}

	//-------------------------------------------------------------------------
	template <class Type>
	void BinaryStream::pack(const Type& inspectableObject)
	{
		write(inspectableObject);
		_firstPass = false;
		write(inspectableObject);
		_firstPass = true;
	}

	//-------------------------------------------------------------------------
	inline std::stringstream& BinaryStream::getStream()
	{
		return _binaryStream;
	}

	//-------------------------------------------------------------------------
	inline void	BinaryStream::read(glm::GlmString& value)
	{
		unsigned int size(0);
		read(size);

		void* readBuffer = GLM_ALLOC(sizeof(char) * (size + 1), readBuffer, glm::MemoryStatIds::GolaemShared)
		GLM_ASSERT(readBuffer != NULL);

		char* s = static_cast<char*>(readBuffer);
		read(s, size);
		s[size] = '\0';
		value.assign(s);
		s = NULL;

		GLM_FREE( readBuffer,  glm::MemoryStatIds::GolaemShared);
	}

	//-------------------------------------------------------------------------
	inline void	BinaryStream::write(const glm::GlmString& value)
	{
		if (!_firstPass)
		{
			unsigned int size(static_cast<unsigned int>(value.size()));
			write(size);
			write(value.c_str(), size);
		}
	}

	//-------------------------------------------------------------------------
	template <class T>
	inline void	BinaryStream::read(glm::PODArrayOfArray<T>& value)
	{
		unsigned int capacity_arrays(0);
		read(capacity_arrays);

		unsigned int capacity_items(0);
		read(capacity_items);

		if (_firstPass)
		{
			value.clear();

			if (capacity_arrays > 0 && capacity_items > 0)
			{
				value.reserve(capacity_arrays, capacity_items);
			}

			unsigned int size_arrays = 0;
			read(size_arrays);
			for (unsigned int arrayIndex = 0; arrayIndex < size_arrays; ++arrayIndex)
			{
				unsigned int capacity_array_items = 0;
				read(capacity_array_items);

				unsigned int size_array_items = 0;
				read(size_array_items);
				for (unsigned int itemIndex = 0; itemIndex < size_array_items; ++itemIndex)
				{
					T item;
					read(item);
				}
			}
		}
		else
		{
			unsigned int size_arrays = 0;
			read(size_arrays);
			for (unsigned int arrayIndex = 0; arrayIndex < size_arrays; ++arrayIndex)
			{
				unsigned int capacity_array_items = 0;
				read(capacity_array_items);

				unsigned int size_array_items = 0;
				read(size_array_items);

				value.push_back_array(size_array_items, capacity_array_items);

				for (unsigned int itemIndex = 0; itemIndex < size_array_items; ++itemIndex)
				{
					read(value.at(arrayIndex, itemIndex));
				}

				//NOTE: possible optimization BUT it cuts off any template specialization for BinaryStream::read(T&)
				// 				if (size_array_items > 0)
				// 				{
				// 					char* array_data = reinterpret_cast<char*>(value.begin(arrayIndex));
				// 					unsigned int array_data_size = static_cast<unsigned int>(sizeof(T) * size_array_items);
				// 					read(array_data, array_data_size);
				// 				}
			}
		}


	}

	//-------------------------------------------------------------------------
	template <class T>
	inline void	BinaryStream::write(const glm::PODArrayOfArray<T>& value)
	{
		if (!_firstPass)
		{
			const unsigned int capacity_arrays = static_cast<unsigned int>(value.capacity_arrays());
			write(capacity_arrays);
			const unsigned int capacity_items = static_cast<unsigned int>(value.capacity_items());
			write(capacity_items);

			const unsigned int size_arrays = static_cast<unsigned int>(value.size_arrays());
			write(size_arrays);
			for (unsigned int arrayIndex = 0; arrayIndex < size_arrays; ++arrayIndex)
			{
				const unsigned int capacity_array_items = static_cast<unsigned int>(value.capacity_items(arrayIndex));
				write(capacity_array_items);

				const unsigned int size_array_items = static_cast<unsigned int>(value.size_items(arrayIndex));
				write(size_array_items);
				for (unsigned int itemIndex = 0; itemIndex < size_array_items; ++itemIndex)
				{
					write(value.at(arrayIndex, itemIndex));
				}

				//NOTE: possible optimization BUT it cuts off any template specialization for BinaryStream::write(const T&)
				// 				if (size_array_items > 0)
				// 				{
				// 					const char* array_data = reinterpret_cast<const char*>(value.begin(arrayIndex));
				// 					unsigned int array_data_size = static_cast<unsigned int>(sizeof(T) * size_array_items);
				// 					write(array_data, array_data_size);
				// 				}
			}
		}
	}

	//-------------------------------------------------------------------------
	template<class FirstType, class SecondType>
	inline void	BinaryStream::read(std::pair<FirstType, SecondType> & value)
	{
		read(value.first);
		read(value.second);
	}

	//-------------------------------------------------------------------------
	template<class FirstType, class SecondType>
	inline void	BinaryStream::write(const std::pair<FirstType, SecondType> & value)
	{
		write(value.first);
		write(value.second);
	}

	//-------------------------------------------------------------------------
	template <class Type>
	inline void	BinaryStream::read(glm::GlmList<Type>& value)
	{
		unsigned int size(0);
		read(size);
		if (_firstPass)
		{
			value.clear();
			value.resize(size);
		}
		typename glm::GlmList<Type>::iterator ite(value.begin());
		while (ite != value.end())
		{
			read(*ite);
			ite++;
		}
	}

	//-------------------------------------------------------------------------
	template <class Type>
	inline void BinaryStream::write(const glm::GlmList<Type>& value)
	{
		write(static_cast<unsigned int>(value.size()));
		typename glm::GlmList<Type>::const_iterator ite(value.begin());
		for (; ite != value.end(); ++ite)
		{
			write(*ite);
		}
	}

	//-------------------------------------------------------------------------
	template <class Type, class Comparator>
	inline void BinaryStream::read(std::set<Type, Comparator> & value)
	{
		unsigned int size(0);
		read(size);

		// std::set are not usable with IInspectable objects having dynamicArrayAttributes or pointers, as they overwrite the first pass object in second pass, messing potential arrays and pointers
		value.clear();
		Type element;
		for (unsigned int i(0); i < size; ++i)
		{
			read(element);
			value.insert(element);
		}
	}

	//-------------------------------------------------------------------------
	template <class Type, class Comparator>
	inline void	BinaryStream::write(const std::set<Type, Comparator>& value)
	{
		write(static_cast<unsigned int>(value.size()));
		for (typename std::set<Type, Comparator>::const_iterator it = value.begin(); it != value.end(); ++it)
		{
			write(*it);
		}
	}

	//-------------------------------------------------------------------------
	template <class KeyType, class Type, class Comparator>
	inline void	BinaryStream::read(std::map<KeyType, Type, Comparator> & value)
	{
		unsigned int size(0);
		read(size);
		if (_firstPass)
		{
			value.clear();
		}
		KeyType key;
		for (unsigned int i(0); i < size; ++i)
		{
			read(key);
			read(value[key]);
		}
	}

	//-------------------------------------------------------------------------
	template <class KeyType, class Type, class Comparator>
	inline void	BinaryStream::write(const std::map<KeyType, Type, Comparator>& value)
	{
		write(static_cast<unsigned int>(value.size()));
		for (typename std::map<KeyType, Type, Comparator>::const_iterator it = value.begin(); it != value.end(); ++it)
		{
			write(it->first);
			write(it->second);
		}
	}

	//-------------------------------------------------------------------------
	template <class Type>
	inline void BinaryStream::read(glm::GlmSet<Type> & value)
	{
		unsigned int size(0);
		read(size);

		// glm::GlmSet are not usable with IInspectable objects having dynamicArrayAttributes or pointers, as they overwrite the first pass object in second pass, messing potential arrays and pointers
		value.clear();
		Type element;
		for (unsigned int i(0); i < size; ++i)
		{
			read(element);
			value.insert(element);
		}
	}

	//-------------------------------------------------------------------------
	template <class Type>
	inline void	BinaryStream::write(const glm::GlmSet<Type>& value)
	{
		write(static_cast<unsigned int>(value.size()));
		for (typename glm::GlmSet<Type>::const_iterator it = value.begin(); it != value.end(); ++it)
		{
			write(*it);
		}
	}

	//-------------------------------------------------------------------------
	template <class KeyType, class Type>
	inline void	BinaryStream::read(glm::GlmMap<KeyType, Type> & value)
	{
		unsigned int size(0);
		read(size);
		if (_firstPass)
		{
			value.clear();
		}
		KeyType key;
		for (unsigned int i(0); i < size; ++i)
		{
			read(key);
			read(value[key]);
		}
	}

	//-------------------------------------------------------------------------
	template <class KeyType, class Type>
	inline void	BinaryStream::write(const glm::GlmMap<KeyType, Type>& value)
	{
		write(static_cast<unsigned int>(value.size()));
		for (typename glm::GlmMap<KeyType, Type>::const_iterator it = value.begin(); it != value.end(); ++it)
		{
			write(it.getKey());
			write(it.getValue());
		}
	}

	//-------------------------------------------------------------------------
	// read SmartPointer
	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::read(SmartPointer<TPointeeType>& value)
	{
		TPointeeType* pointer(NULL);
		read(pointer);

		value = pointer; // this may produce much more SP than necessary, but this is the simplest way to do it, else we must handle the correct ref count in every case which is not the case now.
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::write(const SmartPointer<TPointeeType>& value)
	{
		write(value.getImpl());
	}

	//-------------------------------------------------------------------------
	template <class TValue >
	inline void	BinaryStream::doReadAttribute(TValue& value)
	{
		_binaryStream.read(reinterpret_cast<char*>(&value), sizeof(TValue));
	}

	//-------------------------------------------------------------------------
	template <class TValue >
	inline void	BinaryStream::doWriteAttribute(TValue value)
	{
		if (!_firstPass)
		{
			_binaryStream.write(reinterpret_cast<char*>(&value), sizeof(TValue));
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::doRead(TPointeeType*& value)
	{
		unsigned int pointerId;
		read(pointerId);
		bool readValue;
		read(readValue);

		if (_firstPass)
		{
			if (_idToPointerMap.find(pointerId) == _idToPointerMap.end())
			{
				GLM_DEBUG_ASSERT(value == NULL);
				value = new TPointeeType();
				_idToPointerMap[pointerId] = value;
				read(*value);
			}
		}
		value = static_cast<TPointeeType*>(_idToPointerMap[pointerId]);
		if (!_firstPass && readValue)
		{
			read(*value);
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::doWrite(TPointeeType* value)
	{
		if (!_firstPass)
		{
			unsigned int pointerId(0);
			if (_pointerToIdMap.find(value) == _pointerToIdMap.end())
			{
				pointerId = static_cast<unsigned int>(_pointerToIdMap.size());
				write(pointerId);
				write(true);
				_pointerToIdMap[value] = pointerId;
				write(*value);
			}
			else
			{
				pointerId = _pointerToIdMap[value];
				write(pointerId);
				write(false);
			}
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::doRead(const TPointeeType*& value)
	{
		unsigned int pointerId;
		read(pointerId);
		bool readValue;
		read(readValue);

		if (_firstPass)
		{
			if (_idToPointerMap.find(pointerId) == _idToPointerMap.end())
			{
				GLM_DEBUG_ASSERT(value == NULL);
				value = new TPointeeType();
				_idToPointerMap[pointerId] = const_cast<TPointeeType*>(value);
				read(*const_cast<TPointeeType*>(value));
			}
		}
		value = static_cast<TPointeeType*>(_idToPointerMap[pointerId]);
		if (!_firstPass && readValue)
		{
			read(*const_cast<TPointeeType*>(value));
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::doWrite(const TPointeeType* value)
	{
		if (!_firstPass)
		{
			unsigned int pointerId(0);
			if (_pointerToIdMap.find(const_cast<TPointeeType*>(value)) == _pointerToIdMap.end())
			{
				pointerId = static_cast<unsigned int>(_pointerToIdMap.size());
				write(pointerId);
				write(true);
				_pointerToIdMap[const_cast<TPointeeType*>(value)] = pointerId;
				write(*value);
			}
			else
			{
				pointerId = _pointerToIdMap[const_cast<TPointeeType*>(value)];
				write(pointerId);
				write(false);
			}
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::read(TPointeeType*& value)
	{
		unsigned int pointerId;
		read(pointerId);
		if (pointerId != 0)
		{
			TypeID typeID;
			read(typeID);
			bool readValue;
			read(readValue);
			if (_firstPass)
			{
				if ((_idToPointerMap.find(pointerId) == _idToPointerMap.end()) && (pointerId != 0))
				{
					value = static_cast<TPointeeType*>(RTTI::createInstance(typeID));
					if (value != NULL)
					{
						_idToPointerMap[pointerId] = value;
						if (readValue)
						{
							read(*value);
						}
					}
					else
					{
						GLM_SDK_TRACE_WARNING(typeID.getString().c_str() << " is not a registered inspectable type");
					}
				}
			}
			else
			{
				value = static_cast<TPointeeType*>(_idToPointerMap[pointerId]);
				if (value != NULL && readValue)
				{
					read(*value);
				}
			}
		}
		else
		{
			value = NULL;
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void BinaryStream::write(TPointeeType* value)
	{
		if (_firstPass == true)
		{
			if (_pointerToIdMap.find(value) == _pointerToIdMap.end())
			{
				const RTTI& rtti = value->getRTTI();
				unsigned int pointerId(static_cast<unsigned int>(_pointerToIdMap.size()));
				_pointerToIdMap[value] = pointerId;
				_pointersValueToWrite.push_back(TypedPointer(value, rtti.getTypeID()));
				glmQuickSort(_pointersValueToWrite.begin(), _pointersValueToWrite.end());
				_pointersValueToWrite.resize( glmUnique(_pointersValueToWrite.begin(), _pointersValueToWrite.end()) - _pointersValueToWrite.begin(), TypedPointer(value, rtti.getTypeID()));
				for (unsigned int i(0); i < rtti.getAttributesCount(); ++i)
				{
					const AbstractAttribute* attribute = rtti.getAttribute(i);
					attribute->pack(*this, value);
				}
			}
		}
		else
		{
			unsigned int pointerId(_pointerToIdMap[value]);
			write(pointerId);
			if (value != NULL)
			{
				const RTTI& rtti = value->getRTTI();
				write(rtti.getTypeID());
				if (glmBinarySearch(_pointersValueToWrite.begin(), _pointersValueToWrite.end(), TypedPointer(value, rtti.getTypeID())) != _pointersValueToWrite.end())
				{
					write(true);
					write(*value);
				}
				else
				{
					write(false);
				}
			}
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::read(const TPointeeType*& value)
	{
		unsigned int pointerId;
		read(pointerId);
		if (pointerId != 0)
		{
			TypeID typeID;
			read(typeID);
			bool readValue;
			read(readValue);
			if (_firstPass)
			{
				if ((_idToPointerMap.find(pointerId) == _idToPointerMap.end()) && (pointerId != 0))
				{
					value = static_cast<TPointeeType*>(RTTI::createInstance(typeID));
					if (value != NULL)
					{
						_idToPointerMap[pointerId] = const_cast<TPointeeType*>(value);
						if (readValue)
						{
							read(*const_cast<TPointeeType*>(value));
						}
					}
					else
					{
						GLM_SDK_TRACE_WARNING(typeID.getString().c_str() << " is not a registered inspectable type");
					}
				}
			}
			else
			{
				value = static_cast<TPointeeType*>(_idToPointerMap[pointerId]);
				if (value != NULL && readValue)
				{
					read(*const_cast<TPointeeType*>(value));
				}
			}
		}
		else
		{
			value = NULL;
		}
	}

	//-------------------------------------------------------------------------
	template <class TPointeeType>
	inline void	BinaryStream::write(const TPointeeType* value)
	{
		if (_firstPass)
		{
			if (_pointerToIdMap.find(const_cast<TPointeeType*>(value)) == _pointerToIdMap.end())
			{
				const RTTI& rtti = value->getRTTI();
				unsigned int pointerId(static_cast<unsigned int>(_pointerToIdMap.size()));
				_pointerToIdMap[const_cast<TPointeeType*>(value)] = pointerId;
				_pointersValueToWrite.push_back(TypedPointer(value, rtti.getTypeID()));
				glmQuickSort(_pointersValueToWrite.begin(), _pointersValueToWrite.end());
				_pointersValueToWrite.resize( glmUnique(_pointersValueToWrite.begin(), _pointersValueToWrite.end()) - _pointersValueToWrite.begin(), TypedPointer(value, rtti.getTypeID()));
				for (unsigned int i(0); i < rtti.getAttributesCount(); ++i)
				{
					const AbstractAttribute* attribute = rtti.getAttribute(i);
					attribute->pack(*this, value);
				}
			}
		}
		else
		{
			unsigned int pointerId(_pointerToIdMap[const_cast<TPointeeType*>(value)]);
			write(pointerId);
			if (value != NULL)
			{
				const RTTI& rtti = value->getRTTI();
				write(rtti.getTypeID());
				if (glmBinarySearch(_pointersValueToWrite.begin(), _pointersValueToWrite.end(), TypedPointer(value, rtti.getTypeID())) != _pointersValueToWrite.end())
				{
					write(true);
					write(*value);
				}
				else
				{
					write(false);
				}
			}
		}
	}

	//-------------------------------------------------------------------------
	inline bool	BinaryStream::isFirstPass()
	{
		return _firstPass;
	}

	//-------------------------------------------------------------------------
	inline void	BinaryStream::setFirstPass(bool firstPass)
	{
		_firstPass = firstPass;
	}

	//-------------------------------------------------------------------------
	inline void BinaryStream::pushValueInFifo(void* value)
	{
		_queueOfAccessorAttributeValues.push(value);
	}

	//-------------------------------------------------------------------------
	inline void* BinaryStream::popValuefromFifo()
	{
		void* returnValue = _queueOfAccessorAttributeValues.front();
		_queueOfAccessorAttributeValues.pop();
		return returnValue;
	}

	//-------------------------------------------------------------------------
	template <class Type>
	void BinaryStream::read(Type& value)
	{
		const RTTI& rtti = value.getRTTI();
		unsigned int pointerId(0);
		read(pointerId);
		if (_firstPass)
		{
			glm::GlmMap<unsigned int, void*>::iterator ite(_idToPointerMap.find(pointerId));
			if ((ite != _idToPointerMap.end()) && (ite.getValue() != &value))
			{
				delete static_cast<Type*>(ite.getValue());
			}
			_idToPointerMap[pointerId] = &value;
		}

		unsigned int attributesCount(0);
		read(attributesCount);

		GLM_ASSERT_MSG(rtti.getAttributesCount() == attributesCount,
			"the Inspectable object of type : %s passed as an argument to BinaryStream::read has a different attributes count, %lu, than the one being read from the stream, %lu",
			rtti.getTypeName(), rtti.getAttributesCount(), attributesCount);

		for (unsigned int i(0); i < attributesCount; ++i)
		{
			const AbstractAttribute* attribute = rtti.getAttribute(i);
			attribute->unpack(*this, &value);
		}
	}

	//-------------------------------------------------------------------------
	template <class Type>
	void BinaryStream::write(const Type& value, bool isValue)
	{
		const RTTI& rtti = value.getRTTI();
		unsigned int pointerId(0);
		if (isValue)
		{
			glm::Array<TypedPointer>::iterator ite = glmBinarySearch(_pointersValueToWrite.begin(), _pointersValueToWrite.end(), TypedPointer(&value, rtti.getTypeID()));
			if (ite != _pointersValueToWrite.end())
			{
				_pointersValueToWrite.erase(ite);
			}
		}
		if (_firstPass)
		{
			if (_pointerToIdMap.find(&value) == _pointerToIdMap.end())
			{
				pointerId = static_cast<unsigned int>(_pointerToIdMap.size());
				_pointerToIdMap[&value] = pointerId;
			}
		}
		else
		{
			pointerId = _pointerToIdMap[&value];
			write(pointerId);
			write(rtti.getAttributesCount());
		}
		for (unsigned int i(0); i < rtti.getAttributesCount(); ++i)
		{
			const AbstractAttribute* attribute = rtti.getAttribute(i);
			attribute->pack(*this, &value);
		}
	}
}
