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

#pragma once

#include "glmAssert.h"
#include "glmTypes.h"
#include "glmMemory.h"
#include "glmArray.h"
#include "glmPODArray.h"

#include "glmContainersUtils.h"

namespace glm
{
    // provides a pool of items to avoid reallocating each frame, recycling deleted nodes. Allocate per static arrays to limit allocation calls
    // will grow by reserving each time 8+(2*prev) size, as std::vector
    // if SkipConstructorDestructorAtRecycling is true (default), the item is constructed and destructed at each recycling cycle. It is stored already destructed. Clear does not call destruction but just free the memory of the items. It is user responsability to recycle everything before destroying this object
    // if SkipConstructorDestructorAtRecycling is false, items are not destructed when recycled and not re-constructed except at first allocation. clear calls destruction on all items.
    template <typename T, bool SkipConstructorDestructorAtRecycling = false>
    class RecyclerT
    {
        GLM_CLASS_STAT_ID( glm::MemoryStatIds::GolaemShared)

    public:
        // ---------------------------------------------------------------------
        RecyclerT(size_t initialAllocationSize = 8, const MemAllocStatId memAllocStatId = glm::MemoryStatIds::GolaemShared)
            : _allocatedDataArrays(memAllocStatId)
            , _recycledItems(memAllocStatId)
            , _currentFeedingIndex(0)
            , _allocationSize(initialAllocationSize)
			, _memAllocStatId(memAllocStatId)
        {
        }

        // ---------------------------------------------------------------------
        ~RecyclerT()
        {
            clear();
        }

        // ---------------------------------------------------------------------
        T* obtain()
        {			
            if (!_recycledItems.empty())
            {
                T* recycledItem = _recycledItems.back();
                _recycledItems.pop_back();
                if (!SkipConstructorDestructorAtRecycling)
                {
                    new (recycledItem) T(); // call constructor only when really (re)using it
                }
                
                return recycledItem;
            }
            else if (_allocatedDataArrays.empty() || _currentFeedingIndex == _allocationSize)
            {
                // don't grow on first call if size already set, use initial size instead
                if (_allocationSize == 0 || !_allocatedDataArrays.empty())
                {
                    _allocationSize = _allocationSize * 2 + 8;
                }
				void* newValue = GLM_ALLOC(_allocationSize * sizeof(T), newValue, _memAllocStatId);
                T* newItemArray = static_cast<T*>(newValue);
                _allocatedDataArrays.push_back(newItemArray);
                _currentFeedingIndex = 0;
            }

            T* newItem = &(_allocatedDataArrays.back()[_currentFeedingIndex]);
            new (newItem) T(); // call constructor only when really (re)using it
            _currentFeedingIndex++;
            return newItem;
        }

        // ---------------------------------------------------------------------
        void recycle(T* itemToRecycle)
        {
            if (!SkipConstructorDestructorAtRecycling)
            {
                itemToRecycle->~T();
            }
            _recycledItems.push_back(itemToRecycle);
        }

        // ---------------------------------------------------------------------
        void clear()
        {
            // we need to destruct every item recycled (= actually newed and used in the not constructor)
            if (SkipConstructorDestructorAtRecycling)
            {
                for (auto& itemToRecycle : _recycledItems)
                {
                    itemToRecycle->~T();
                }                
            }

            // actually delete data arrays. beware that it also delete returned items not yet recycled if any (should be called after recycler user clear only) !
            for (size_t i = 0; i < _allocatedDataArrays.size(); i++)
            {
                GLM_FREE( _allocatedDataArrays[i],  _memAllocStatId);
            }
            _allocatedDataArrays.clear();
			_recycledItems.clear();
            _currentFeedingIndex = 0;
			_allocationSize = 0;
        }

        // ---------------------------------------------------------------------
		MemAllocStatId getMemAllocStatId() const
        {
            return _memAllocStatId;
        }

    private:
        glm::PODArray<T*> _allocatedDataArrays; // alloc an array of initialSize, when full, another array of 2xinitialSize+8, etc.
        glm::PODArray<T*> _recycledItems; // will push recycled items as void* in a list
        size_t _currentFeedingIndex;
        size_t _allocationSize; // current allocation size, and size to allocate

        MemAllocStatId _memAllocStatId;
    };

} // namespace glm