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

#pragma once

#include "glmCoreAPI.h"
#include "glmTypes.h"
#include "glmAssert.h"
#include "glmMemory.h"
#include "glmCyclicNode.h"

namespace glm
{
	/** 
	* \brief Cyclic node allocator.
	*
	* This class optimizes the cyclic nodes management using an allocation cache.
	*
	* \ingroup memory
	* \author Golaem <dev@golaem.com>
	*
	* \param T The type of data contained in the build nodes.
	* \warning The data contained in the nodes are not modified when creating or recycling nodes.
	*/
	template <class T, MemAllocStatId M>
	class CyclicNodeAllocator
	{
	public:
		/** 
		* \brief Constructor
		*/
		CyclicNodeAllocator();

		/** 
		* \brief Destructor 
		*/
		~CyclicNodeAllocator();

		/** 
		* \brief Create the head node and register its end of program termination.
		*/
		void createHead();

		/** 
		* \brief Terminate the node 
		*/
		static void terminate();

		/** 
		* \brief Obtain a new node.
		* \warning A dynamic allocation is performed if the freen node cache is empty.
		*/
		CyclicNode<T> * obtain();

		/** 
		* \brief Obtain a new node encapsulating the given data.
		* \warning A dynamic allocation is performed if the freen node cache is empty.
		*/
		CyclicNode<T> * obtain(const T & data);

		/** 
		* \brief Recycle a given node.
		*/
		void recycle(CyclicNode<T> * node);

		/** 
		* \brief Clear the memory used by free nodes.
		*/
		void clearPreallocatedMemory();
	protected:
		/** 
		* \brief Head of available preallocated nodes.
		*/
		CyclicNode<T> _head ;
	} ;

	//*****************************************************************************
	// CyclicNodeAllocator definitions
	//*****************************************************************************

	//-----------------------------------------------------------------------------
	// constructor
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	CyclicNodeAllocator<T, M>::CyclicNodeAllocator()
	{
	}

	//-----------------------------------------------------------------------------
	// destructor
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	CyclicNodeAllocator<T, M>::~CyclicNodeAllocator()
	{
		CyclicNode<T> * node;
		CyclicNode<T> * nextNode;

		while( (node = _head.next()) != &_head )
		{
			nextNode = node->next();
			GLM_DELETE(node,  M);
			node = nextNode;
		}
	}

	//-----------------------------------------------------------------------------
	// obtain
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	CyclicNode<T> * CyclicNodeAllocator<T, M>::obtain()
	{
		if(_head.next()==&_head)
		{
			CyclicNode<T>* node = GLM_NEW( CyclicNode<T>, node, M);
			return node;
		}
		CyclicNode<T> * node = _head.next() ;
		node->detach() ;
		node->touchData().~T() ;
		new (&node->touchData()) T ;

		return node ;
	}

	//-----------------------------------------------------------------------------
	// obtain (with a given value)
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	CyclicNode<T> * CyclicNodeAllocator<T, M>::obtain(const T & data)
	{
		if(_head.next()==&_head)
		{
			CyclicNode<T>* node = GLM_NEW(CyclicNode<T>, node, M,  data);
			return node;
		}
		CyclicNode<T>* node = _head.next() ;
		node->detach() ;
		node->touchData().~T() ;
		new (&node->touchData()) T(data) ;
		return node ;			
	}

	//-----------------------------------------------------------------------------
	// recycle
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	void CyclicNodeAllocator<T, M>::recycle(CyclicNode<T> * node)
	{
		_head.insertAfter(node) ;
	}

	//-----------------------------------------------------------------------------
	// clearPreallocatedMemory
	//-----------------------------------------------------------------------------
	template <class T, MemAllocStatId M>
	void CyclicNodeAllocator<T, M>::clearPreallocatedMemory()
	{
		while(_head.next()!=&_head)
		{
			CyclicNode<T> * tmp = obtain() ;
			GLM_DELETE(tmp,  M);
		}
	}
}
