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

*/

#ifndef _GMMEMFIXEDSET_H_
#define _GMMEMFIXEDSET_H_

#include "gmMemFixed.h"
#include "gmListDouble.h"


/// \class gmMemFixedSet
/// \brief Fixed memory allocator, Allows varying size allocations using a set of fixed allocators.
class gmMemFixedSet
{
public:

  inline gmMemFixedSet();
  inline ~gmMemFixedSet();

  /// \brief Alloc() an element
  /// \param a_size size of allocation
  inline void* Alloc(int a_size);
  
  /// \brief Free() an element
  /// \param a_size size of allocation
  inline void Free(void* a_ptr);
  
  /// \brief Reset()
  void Reset();
  
  /// \brief ResetAndFreeMemory()
  void ResetAndFreeMemory();

  /// \brief GetMemUsed() will return the number of bytes allocated and returned, not
  ///        the total number of bytes allocated by the memFixedSet object.
  inline unsigned int GetMemUsed() const { return m_memUsed; }

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

  /// \brief Presize() will presize the memfixed pools
  void Presize(int a_pool8,
               int a_pool16,
               int a_pool24,
               int a_pool32,
               int a_pool64,
               int a_pool128,
               int a_pool256,
               int a_pool512
              );

  /// \brief PrintStats() will print out the stats for gm memory
  void PrintStats() const;

protected:

  /// \brief Internal data structure for small allocations
  struct SmallMemNode
  {
    // NOTE: IMPORTANT size is NOT stored in m_size but (int*)Data()[-1]
    //       This allows SmallMemNode and BigMemNode to both check the same memory offset to determine size
    //       We must allocate AT LEAST enough memory for the size to be stored.
    private: int m_size_reserved;                         ///< Allocation size (WARNING: Do not directly access)
    public: char* Data() {return (char*)(this + 1);}      ///< Get ptr after this structure
  };

  /// \brief Internal data structure for large allocations
  struct BigMemNode: public gmListDoubleNode<BigMemNode>
  { 
    // NOTE: IMPORTANT size is NOT stored in m_size but (int*)Data()[-1]
    //       This allows SmallMemNode and BigMemNode to both check the same memory offset to determine size
    //       We must allocate AT LEAST enough memory for the size to be stored.
    private: int m_size_reserved;                         ///< Allocation size (WARNING: Do not directly access)
    public: char* Data() {return (char*)(this + 1);}      ///< Get ptr after this structure
  };

  void FreeBigAllocs();                           ///< Free the big allocations
  inline SmallMemNode* GetSmallNodeData(void* a_ptr)  { return ((SmallMemNode*)a_ptr)-1; }
  inline BigMemNode* GetBigNodeData(void* a_ptr)  { return ((BigMemNode*)a_ptr)-1; }

  // NOTE: Pass in the DATA ptr, NOT the Node ptr
  inline int GetDataAllocationSize(void* a_ptr) const
  {
    int* ptrToSize = (int*)a_ptr;
    return ptrToSize[-1]; 
  }
  // NOTE: Pass in the DATA ptr, NOT the Node ptr
  inline void SetDataAllocationSize(void* a_ptr, int a_size)
  {
    int* ptrToSize = (int*)a_ptr;
    ptrToSize[-1] = a_size; 
  }
  inline void SetNodeDataSize(SmallMemNode* a_ptr, int a_size)
  {
    SetDataAllocationSize(a_ptr->Data(), a_size);
  }
  inline void SetNodeDataSize(BigMemNode* a_ptr, int a_size)
  {
    SetDataAllocationSize(a_ptr->Data(), a_size);
  }


  gmMemFixed m_mem8;                              ///< Memory for 8 bytes and less
  gmMemFixed m_mem16;                             ///< Memory for 16 bytes and less
  gmMemFixed m_mem24;                             ///< Memory for 24 bytes and less
  gmMemFixed m_mem32;                             ///< Memory for 32 bytes and less
  gmMemFixed m_mem64;                             ///< Memory for 64 bytes and less
  gmMemFixed m_mem128;                            ///< Memory for 128 bytes and less
  gmMemFixed m_mem256;                            ///< Memory for 256 bytes and less
  gmMemFixed m_mem512;                            ///< Memory for 512 bytes and less
  gmListDouble<BigMemNode> m_bigAllocs;           ///< List holding memory for more than 512 bytes
  int m_memUsed;

};


gmMemFixedSet::gmMemFixedSet()
  : m_mem8(8 + sizeof(SmallMemNode),     64),
    m_mem16(16 + sizeof(SmallMemNode),   64),
    m_mem24(24 + sizeof(SmallMemNode),   64),
    m_mem32(32 + sizeof(SmallMemNode),   64),
    m_mem64(64 + sizeof(SmallMemNode),   32),
    m_mem128(128 + sizeof(SmallMemNode), 32),
    m_mem256(256 + sizeof(SmallMemNode), 16),
    m_mem512(512 + sizeof(SmallMemNode), 16)
{
  m_memUsed = 0;
}


gmMemFixedSet::~gmMemFixedSet()
{
  FreeBigAllocs();
}


void* gmMemFixedSet::Alloc(int a_size)
{
  SmallMemNode* node;

  if (a_size <= 32)
  {
    if (a_size <= 8)
    {
      node = (SmallMemNode*)m_mem8.Alloc();
      SetNodeDataSize(node, 8);
      m_memUsed += 8;
    }
    else if (a_size <= 16)
    {
      node = (SmallMemNode*)m_mem16.Alloc();
      SetNodeDataSize(node, 16);
      m_memUsed += 16;
    }
    else if (a_size <= 24)
    {
      node = (SmallMemNode*)m_mem24.Alloc();
      SetNodeDataSize(node, 24);
      m_memUsed += 24;
    }
    else // if (a_size <= 32)
    {
      GM_ASSERT(a_size <= 32);

      node = (SmallMemNode*)m_mem32.Alloc();
      SetNodeDataSize(node, 32);
      m_memUsed += 32;
    }
  }
  else
  {
    if (a_size <= 64)
    {
      node = (SmallMemNode*)m_mem64.Alloc();
      SetNodeDataSize(node, 64);
      m_memUsed += 64;
    }
    else if (a_size <= 128)
    {
      node = (SmallMemNode*)m_mem128.Alloc();
      SetNodeDataSize(node, 128);
      m_memUsed += 128;
    }
    else if (a_size <= 256)
    {
      node = (SmallMemNode*)m_mem256.Alloc();
      SetNodeDataSize(node, 256);
      m_memUsed += 256;
    }
    else if (a_size <= 512)
    {
      node = (SmallMemNode*)m_mem512.Alloc();
      SetNodeDataSize(node, 512);
      m_memUsed += 512;
    }    
    else
    {
      BigMemNode* bigNode;

      bigNode = (BigMemNode*)GM_NEW( char[a_size + sizeof(*bigNode)] ); // This will be aligned as it calls sys new

      m_bigAllocs.InsertFirst(bigNode);
      SetNodeDataSize(bigNode, a_size);
      m_memUsed += a_size;

      return bigNode->Data();
    }
  }

  return node->Data();
}


void gmMemFixedSet::Free(void* a_ptr)
{
  SmallMemNode* node = GetSmallNodeData(a_ptr); // NOTE: Only valid for small nodes
  int size = GetDataAllocationSize(a_ptr); // Pass the data ptr, not the node ptr so we can offset correctly

  if (size <= 32)
  {
    if (size == 8)
    {
      m_mem8.Free(node);
      m_memUsed -= 8;
    }
    else if (size == 16)
    {
      m_mem16.Free(node);
      m_memUsed -= 16;
    }
    else if (size == 24)
    {
      m_mem24.Free(node);
      m_memUsed -= 24;
    }
    else // if (size == 32)
    {
      GM_ASSERT(size == 32);

      m_mem32.Free(node);
      m_memUsed -= 32;
    }
  }
  else
  {
    if (size == 64)
    {
      m_mem64.Free(node);
      m_memUsed -= 64;
    }
    else if (size == 128)
    {
      m_mem128.Free(node);
      m_memUsed -= 128;
    }
    else if (size == 256)
    {
      m_mem256.Free(node);
      m_memUsed -= 256;
    }
    else if (size == 512)
    {
      m_mem512.Free(node);
      m_memUsed -= 512;
    }
    else
    {
      BigMemNode* bigNode = GetBigNodeData(a_ptr);
      m_memUsed -= size;
      m_bigAllocs.Remove(bigNode);
      delete [] (char*) bigNode;
    }
  }
    
  GM_ASSERT(m_memUsed >= 0);
}

#endif // _GMMEMFIXEDSET_H_
