/*
    _____               __  ___          __            ____        _      __
   / ___/__ ___ _  ___ /  |/  /__  ___  / /_____ __ __/ __/_______(_)__  / /_
  / (_ / _ `/  ' \/ -_) /|_/ / _ \/ _ \/  '_/ -_) // /\ \/ __/ __/ / _ \/ __/
  \___/\_,_/_/_/_/\__/_/  /_/\___/_//_/_/\_\\__/\_, /___/\__/_/ /_/ .__/\__/
                                               /___/             /_/
                                             
  See Copyright Notice in gmMachine.h

*/

#ifndef _GMMEMFIXED_H_
#define _GMMEMFIXED_H_

#include "gmMemChain.h"

#define GM_DEBUG_MEMFIXED 0 // Enable some debugging code, should be disabled unless developing or testing this code

/// \class gmMemFixed
/// \brief Fixed memory allocator, wrapper on chain memory allocator to provide memory reuse. 
///        Performance note: use can cause more random memory access
class gmMemFixed
{
public:

  inline gmMemFixed(unsigned int a_elementSize, unsigned int a_growSize = 64);
  inline ~gmMemFixed();

  /// \brief Alloc() an element
  inline void* Alloc();
  
  /// \brief Free() an element
  inline void Free(void* a_ptr);
  
  /// \brief Reset()
  inline void Reset();
  
  /// \brief ResetAndFreeMemory()
  inline void ResetAndFreeMemory();

  /// \brief Presize
  inline void Presize(int a_kbytes)               { m_memChain.Presize(a_kbytes); }

  inline unsigned int GetElementSize();

  /// \brief GetSystemMemUsed will return the number of bytes allocated by the system.
  inline unsigned int GetSystemMemUsed() const { return m_memChain.GetSystemMemUsed(); }

#ifdef GM_DEBUG_BUILD
  /// \brief GetMemUsed()
  inline unsigned int GetMemUsed() const { return m_memUsed; }
#endif // GM_DEBUG_BUILD

protected:

  // Free list node structure to simplify coding of recycle list.
  struct FreeListNode
  {
    FreeListNode * m_next;
  };

  FreeListNode* m_freeList;                  //!< List of memory block we can reuse
  gmMemChain m_memChain;                     //!< The chain memory used to actually allocate chunks

#ifdef GM_DEBUG_BUILD
  int m_memUsed;
#endif // GM_DEBUG_BUILD
};



gmMemFixed::gmMemFixed(unsigned int a_elementSize, unsigned int a_growSize)
  : m_memChain(a_elementSize, a_growSize)
{
  GM_ASSERT(a_elementSize >= sizeof(FreeListNode));
  m_freeList = NULL;
#ifdef GM_DEBUG_BUILD
  m_memUsed = 0;
#endif // GM_DEBUG_BUILD
}



gmMemFixed::~gmMemFixed()
{
  ResetAndFreeMemory();
}



void* gmMemFixed::Alloc()
{
  void* newMemPtr; 
  
  //Is one available on the free list?
  newMemPtr = m_freeList;
  if(m_freeList)
  {
    m_freeList = m_freeList->m_next;
  }
  else
  {
    //No, so get chain to alloc a new one
    newMemPtr = m_memChain.Alloc();
  }

#ifdef GM_DEBUG_BUILD
  m_memUsed += m_memChain.GetElementSize();
#endif // GM_DEBUG_BUILD

#if GM_DEBUG_MEMFIXED
  // clear new mem pointer to 0xB00BFEED
  int * n = (int *) newMemPtr;
  int c = m_memChain.GetElementSize() / sizeof(int);
  while(c--) *(n++) = 0xB00BFEED;
#endif //GM_DEBUG_MEMFIXED

  return newMemPtr;
}



void gmMemFixed::Free(void* a_ptr)
{
#if GM_DEBUG_MEMFIXED
  // make sure a_ptr is not already in list (freeing something twice)
  FreeListNode * node = m_freeList;
  while(node)
  {
    GM_ASSERT(a_ptr != node);
    node = node->m_next;
  }
#endif //GM_DEBUG_MEMFIXED

  if(a_ptr)
  {
#if GM_DEBUG_MEMFIXED
    // clear new mem pointer to 0xFEEDFACE
    int * n = (int *) a_ptr;
    int c = m_memChain.GetElementSize() / sizeof(int);
    while(c--) *(n++) = 0xFEEDFACE;
#endif //GM_DEBUG_MEMFIXED

    //Add pointer to free list so we can reuse it
    ((FreeListNode*)a_ptr)->m_next = m_freeList;
    m_freeList = (FreeListNode*)a_ptr;
#ifdef GM_DEBUG_BUILD
    m_memUsed -= m_memChain.GetElementSize();
    GM_ASSERT(m_memUsed >= 0);
#endif // GM_DEBUG_BUILD
  }
}



void gmMemFixed::ResetAndFreeMemory()
{
  m_freeList = NULL;
#ifdef GM_DEBUG_BUILD
  m_memUsed = 0;
#endif // GM_DEBUG_BUILD
  m_memChain.ResetAndFreeMemory();
}



void gmMemFixed::Reset()
{
  m_freeList = NULL;
#ifdef GM_DEBUG_BUILD
  m_memUsed = 0;
#endif // GM_DEBUG_BUILD
  m_memChain.Reset();
}



unsigned int gmMemFixed::GetElementSize()
{
  return (unsigned int)m_memChain.GetElementSize();
}


#endif // _GMMEMFIXED_H_
