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

#pragma once

#include "glmDeque.h"

#include "glmTypes.h"
#include "glmMemory.h"
#include "glmMemoryStats.h"

#include "glmConcurrentDataStructures.h"
#include "glmThreading.h"

#define GLM_MEMORY_POOL_DEFAULT_ELEMENT_COUNT_MAX	2

namespace glm
{
	/**
	* \brief MemoryPools offer the advantage of re-using previously allocated dynamic memory, 
	* therefore improving application performance.
	*
	* MemoryPool simply contains a deque of typed pointers to previously allocated instances of a 
	* given type T. The obtain method returns a pointer to an instance, while the recycle method 
	* returns an instance back to the pool.
	*
	* The memory pool has two modes:
	* \li <b>not keeping instances</b>, in this case, each time a instance is obtained, a placement new is performed to 
	* build a new instance in already allocated memory, each time an instance is recycled, the destructor is called without deallocation.
	* \li <b>keepingInstances</b>, in this case, the instances are kept whithout modifications, its up to the user to set up the object once 
	* obtained ; this version should be faster.
	*
	* \param T The type for which the memory pool is designed.
	* \param keepingInstances Tells if the memory pool is keeping instances.
	*
	* \ingroup memory
	* \author Golaem <dev@golaem.com>
	*/
	template <class T, bool keepingInstances = false>
	class MemoryPool
	{
		typedef glm::MemoryPool<T, keepingInstances> ThisMemoryPool;
	public:

		GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared)

		/**
		* \brief constructor.
		* \param size number of pre-allocated instances of T in the pool.
		*/
		explicit MemoryPool( uint32_t size = 0, uint32_t elementCountMax = GLM_MEMORY_POOL_DEFAULT_ELEMENT_COUNT_MAX ) ;

		/**
		* \brief Destructor.
		*/
		virtual ~MemoryPool() ;

		/**
		* \brief first attempts to re-use an instance that has already been recycled. The
		* pool allocates a new instance if the pool is empty.
		* \return a pointer to an instance of T
		*/
		T* obtain() ;

		/**
		* \brief returns an instance back to the pool.
		* \param poolElem heap allocated T instance
		*/
		void recycle( T*& poolElem ) ;

	private:

		/**
		* \brief copy constructor
		*/
		MemoryPool( const MemoryPool& pool ) ;

		/**
		* \brief assignment operator
		*/
		const MemoryPool& operator=( const MemoryPool& pool ) ;

		typedef MPMC_RingBuffer<T*> InternalContainer;
		typename MemoryPool::InternalContainer _pool;
		uint8_t* _buffer;
	} ;


	//-------------------------------------------------------------------------
	// constructor
	//-------------------------------------------------------------------------
	template <class T, bool keepingInstances>
	MemoryPool<T,keepingInstances>::MemoryPool( uint32_t size, uint32_t elementCountMax )
		: _buffer(NULL)
	{
		GLM_ASSERT(elementCountMax > 0);
		GLM_ASSERT(size <= elementCountMax);

		const size_t bufferSize = MemoryPool::InternalContainer::getBufferSizeToAllocate(elementCountMax);
		uint8_t* buffer = GLM_ALLOC_CAST(uint8_t*, sizeof(uint8_t) * bufferSize, buffer, glm::MemoryStatIds::GolaemShared)
		GLM_ASSERT(buffer != NULL);

		const bool initialized = _pool.initialize(buffer, bufferSize, elementCountMax);
		if (initialized)
		{
			if (size > 0)
			{
				for (unsigned int i = 0 ; i < size ; ++i )
				{
					const bool wasPushed = _pool.try_push( new T() );
					GLM_ASSERT( wasPushed );
				}
			}

			_buffer = buffer;
		}
		else
		{
			GLM_FREE( buffer,  glm::MemoryStatIds::GolaemShared);
		}
	}

	//-------------------------------------------------------------------------
	// destructor
	//-------------------------------------------------------------------------
	template <class T, bool keepingInstances>
	MemoryPool<T,keepingInstances>::~MemoryPool()
	{
		// deallocates the pool instances of T
		T* item;
		while ( ! _pool.wasEmpty() )
		{
			if ( _pool.try_pop(item) )
			{
				::operator delete( item ) ;
			}
		}

		_pool.uninitialize();

		GLM_FREE_NOPROFILING( _buffer,  glm::MemoryStatIds::GolaemShared);
	}

	//-------------------------------------------------------------------------
	// obtain
	//-------------------------------------------------------------------------
	template <class T, bool keepingInstances>
	inline T*
		MemoryPool<T,keepingInstances>::obtain()
	{

		// return a previously allocated instance if the pool is not empty
		if ( !_pool.wasEmpty() )
		{
			T* result = NULL;
			T* item( NULL );
			if ( _pool.try_pop(item) ) // queue may have been emptied in between calls by another thread
			{
				result = ( new( item ) T );
			}
			else
			{
				return new T() ;
			}

			return  result ;
		}
		// allocates and return a new instance if the pool is empty
		else
		{
			return new T() ;
		}
	}

	//-------------------------------------------------------------------------
	// recycle
	//-------------------------------------------------------------------------
	template <class T, bool keepingInstances>
	inline void
		MemoryPool<T,keepingInstances>::recycle( T*& poolElem )
	{
		// explicitly destruct the instance without deallocating it
		poolElem->~T() ;

		// insure that it is pushed.
		GLM_ASSERT( _pool.wasInitialized() );

		bool wasPushed = _pool.try_push( poolElem );
		while ( !wasPushed )
		{
			glm::yieldCurrentThread();

			wasPushed = _pool.try_push( poolElem );
		}
		poolElem = NULL;
	}

	/**
	* \brief MemoryPools specialization for keepinstance = true
	*/
	template <class T>
	class MemoryPool<T, true>
	{
		typedef MPMC_RingBuffer<T*> InternalContainer;
	public:

		//NOTE: size is not handled by multithread safe MemoryPool??? (old comment no idea what it means)
		explicit MemoryPool(uint32_t size = 0, uint32_t elementCountMax = GLM_MEMORY_POOL_DEFAULT_ELEMENT_COUNT_MAX, MemAllocStatId statId = glm::MemoryStatIds::GolaemShared)
			: _buffer(NULL)
		{
			GLM_ASSERT(elementCountMax > 0);
			GLM_ASSERT(size <= elementCountMax);
			
			const size_t bufferSize = MemoryPool::InternalContainer::getBufferSizeToAllocate(elementCountMax);
			uint8_t* buffer = GLM_ALLOC_CAST(uint8_t*, sizeof(uint8_t) * bufferSize, buffer, statId)
			GLM_ASSERT(buffer != NULL);

			const bool initialized = _pool.initialize(buffer, bufferSize, elementCountMax);
			if (initialized)
			{
				_buffer = buffer;
			}
			else
			{
				GLM_FREE( buffer,  glm::MemoryStatIds::GolaemShared);
			}
		}

		virtual ~MemoryPool()
		{
			T* item;
			while ( ! _pool.wasEmpty() )
			{
				if ( _pool.try_pop(item) )
				{
					delete item;
				}
			}

			_pool.uninitialize();

			GLM_FREE_NOPROFILING( _buffer,  glm::MemoryStatIds::GolaemShared);
		}

		T* obtain()
		{
			if ( !_pool.wasEmpty() )
			{
				T* item( NULL );
				if ( _pool.try_pop(item) ) // queue may have been emptied in between calls by another thread
				{
					return item;
				}
				else
				{
					return new T() ;
				}
			}
			else
			{
				return new T() ;
			}
		}

		void recycle( T*& poolElem )
		{
			// insure that it is pushed.
			bool wasPushed = _pool.try_push( poolElem );
			while ( !wasPushed )
			{
				glm::yieldCurrentThread();

				wasPushed = _pool.try_push( poolElem );
			}
			poolElem = NULL;
		}

	private:

		MemoryPool( const MemoryPool& pool ) ;
		const MemoryPool& operator=( const MemoryPool& pool ) ;

		typename MemoryPool::InternalContainer _pool;
		uint8_t* _buffer;
	} ;
}


