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

#pragma once

namespace glm
{
	template< typename T >
	class LifoArray
	{
	public:
		LifoArray() : _countMax(0), _count(0), _arrayOfT(NULL) {}
		~LifoArray()
		{
			GLM_ASSERT( _countMax == 0 );
			GLM_ASSERT( _count == 0 );
			GLM_ASSERT( _arrayOfT == NULL );
		}

		bool initialize(void* buffer, size_t bufferSize, size_t countMax);
		void uninitialize();

		bool push(const T& next);
		T* pushEmpty();

		bool pop();
		bool pop(T& copyOfLast);

		void clear() { _count = 0; }

		const T& getAt(size_t index) const;

		bool isFull() const { return ( getCount() == getCountMax() ); }
		bool isEmpty() const { return ( getCount() == 0 ); }

		GLM_FORCE_INLINE size_t getCount() const { return _count; }
		GLM_FORCE_INLINE size_t getCountMax() const { return _countMax; }
		GLM_FORCE_INLINE const T* getArrayOfT() const { return _arrayOfT; }

		static size_t getBufferSizeToAllocate(size_t countMax);

	private:
		size_t getNextIndexAndIncrementCount();
		size_t getLastIndexAndDecrementCount();

		size_t _countMax;
		size_t _count;
		T* _arrayOfT;
	};

	template< typename T >
	bool LifoArray<T>::initialize(void* buffer, size_t bufferSize, size_t countMax)
	{
		GLM_ASSERT( buffer != NULL );
		GLM_ASSERT( bufferSize > 0 && bufferSize == getBufferSizeToAllocate(countMax) );
		GLM_ASSERT( _countMax == 0 && _count == 0 && _arrayOfT == NULL );

		_countMax = countMax;
		_count = 0;
		_arrayOfT = static_cast<T*>(buffer);

		return true;
	}

	template< typename T >
	void LifoArray<T>::uninitialize()
	{
		_countMax = 0;
		_count = 0;
		_arrayOfT = NULL;
	}

	template< typename T >
	bool LifoArray<T>::push(const T& next)
	{
		GLM_ASSERT( _arrayOfT != NULL );
		GLM_ASSERT( 0 <= _count && _count <= _countMax );

		const bool canPush = !isFull();
		if ( canPush )
		{
			const size_t nextIndex = getNextIndexAndIncrementCount();
			_arrayOfT[nextIndex] = next;
		}

		return canPush;
	}

	template< typename T >
	T* LifoArray<T>::pushEmpty()
	{
		T* last = NULL;
		GLM_ASSERT( _arrayOfT != NULL );
		GLM_ASSERT( 0 <= _count && _count <= _countMax );

		const bool canPush = !isFull();
		if ( canPush )
		{
			const size_t nextIndex = getNextIndexAndIncrementCount();
			last = &_arrayOfT[nextIndex];
		}

		return last;
	}

	template< typename T >
	bool LifoArray<T>::pop()
	{
		GLM_ASSERT( _arrayOfT != NULL );
		GLM_ASSERT( 0 <= _count && _count <= _countMax );

		const bool canPop = !isEmpty();
		if ( canPop )
		{
			getLastIndexAndDecrementCount();
		}

		return canPop;
	}

	template< typename T >
	bool LifoArray<T>::pop( T& copyOfLast )
	{
		GLM_ASSERT( _arrayOfT != NULL );
		GLM_ASSERT( 0 <= _count && _count <= _countMax );

		const bool canPop = !isEmpty();
		if ( canPop )
		{
			const size_t lastIndex = getLastIndexAndDecrementCount();
			copyOfLast = _arrayOfT[lastIndex];
		}

		return canPop;
	}

	template< typename T >
	const T& LifoArray<T>::getAt( size_t index ) const
	{
		GLM_ASSERT( _arrayOfT != NULL );
		GLM_ASSERT( 0 <= _count && _count <= _countMax );
		GLM_ASSERT( 0 <= index && index < _count );

		return _arrayOfT[index];
	}

	template< typename T >
	size_t LifoArray<T>::getBufferSizeToAllocate(size_t countMax)
	{
		GLM_ASSERT( countMax >= 2 );
		return sizeof(T) * countMax;
	}

	template< typename T >
	size_t LifoArray<T>::getNextIndexAndIncrementCount()
	{
		GLM_ASSERT( isFull() == false );
		return _count++;
	}

	template< typename T >
	size_t LifoArray<T>::getLastIndexAndDecrementCount()
	{
		GLM_ASSERT( isEmpty() == false );
		return --_count;
	}
}
