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

*/

#include "gmConfig.h"
#include "gmMemChain.h"
#include "gmMem.h"


gmMemChain::gmMemChain(unsigned int a_elementSize, unsigned int a_numElementsInChunk)
{
  m_chunkSize = a_numElementsInChunk * a_elementSize;
  m_elementSize = a_elementSize;
  m_rootChunk = NULL;
  m_currentChunk = NULL;
}



gmMemChain::~gmMemChain()
{
  FreeChunks();
}



void gmMemChain::FreeChunks()
{
  if(m_rootChunk)
  {
    MemChunk *curChunk;

    curChunk = m_rootChunk;
    while(curChunk)
    {
      MemChunk *chunkToFree;
      
      chunkToFree = curChunk;
      curChunk = curChunk->m_nextChunk;
      
      delete [] (char*)chunkToFree;
    }
  }
}



void gmMemChain::ResetAndFreeMemory()
{
  //Actually free the memory
  FreeChunks();

  //Clear pointers to chunk chain
  m_rootChunk = NULL;
  m_currentChunk = NULL;
}



void gmMemChain::Presize(int a_kbytes)
{
  int numChunks = ((a_kbytes + 1) * 1024) / m_chunkSize;
  MemChunk *curChunk;
  curChunk = m_rootChunk;
  while(curChunk)
  {
    --numChunks;
    curChunk = curChunk->m_nextChunk;
  }
  MemChunk * currentChunk = m_currentChunk;
  while(numChunks > 0)
  {
    NewChunk();
    --numChunks;
  }
  if(currentChunk)
  {
    m_currentChunk = currentChunk;
  }
}


gmMemChain::MemChunk* gmMemChain::NewChunk()
{
  MemChunk* newChunk;

  //Is an unused chunk already allocated?
  if(m_currentChunk && m_currentChunk->m_nextChunk)
  {
    newChunk = m_currentChunk->m_nextChunk;
    newChunk->m_curAddress = newChunk->m_minAddress;    
    m_currentChunk = newChunk;
  }
  else //No, allocate a new one
  {
    char * mem = (char *) GM_NEW( char[sizeof(MemChunk) + m_chunkSize] );
    newChunk = (MemChunk *) mem;
  
    //Allocate memory and set address space
    newChunk->m_minAddress = mem + sizeof(MemChunk);
    newChunk->m_lastAddress = (void*)((gmuptr)newChunk->m_minAddress + m_chunkSize);
    newChunk->m_curAddress = newChunk->m_minAddress;

    //Link new chunk to chain
    newChunk->m_lastChunk = m_currentChunk;
    newChunk->m_nextChunk = NULL;
    if(m_currentChunk)
    {
      m_currentChunk->m_nextChunk = newChunk;
    }
    m_currentChunk = newChunk;
    if(!m_rootChunk)
    {
      m_rootChunk = m_currentChunk;
    }
  }
  return newChunk;
}



void* gmMemChain::Alloc()
{
  void *retPtr;
  
  //Allocate first chunk if none exist
  if(!m_rootChunk)
  {
    NewChunk();
  }

  //Advance ptr, allocate new chunk if necessary
  if(((gmuptr)m_currentChunk->m_curAddress + m_elementSize) > (gmuptr)m_currentChunk->m_lastAddress)
  {
    NewChunk();    
  }
  retPtr = m_currentChunk->m_curAddress;
  m_currentChunk->m_curAddress = (void*)((gmuptr)m_currentChunk->m_curAddress + m_elementSize);

  return retPtr;
}


void* gmMemChain::AllocBytes(unsigned int a_numBytes, unsigned int a_alignNumBytes)
{
  GM_ASSERT((a_numBytes + a_alignNumBytes) <= m_chunkSize); // Chunk size is too small to alloc that many elements at once and should be increased

  void *retPtr;
  
  //Allocate first chunk if none exist
  if(!m_rootChunk)
  {
    NewChunk();
  }

  //Align and record the pointer.
  retPtr = _gmAlignMem(m_currentChunk->m_curAddress, a_alignNumBytes);

  //Create new chunk if not enough memory in this one.
  if(((gmuptr)retPtr + a_numBytes) > (gmuptr)m_currentChunk->m_lastAddress)
  {
    NewChunk();    
    
    //Align and record the pointer.
    retPtr = _gmAlignMem(m_currentChunk->m_curAddress, a_alignNumBytes);
  }
  
  //Advance memory ptr for next allocation.
  m_currentChunk->m_curAddress = (void*)((gmuptr)retPtr + a_numBytes);

  return retPtr;
}



void* gmMemChain::Alloc(unsigned int a_numElementsToAlloc)
{
  unsigned int allocSize;
  void *retPtr;
  
  //Allocate first chunk if none exist
  if(!m_rootChunk)
  {
    NewChunk();
  }

  //Advance ptr, allocate new chunk if necessary
  allocSize = a_numElementsToAlloc * m_elementSize;

  GM_ASSERT(allocSize <= m_chunkSize); // Chunk size is too small to alloc that many elements at once and should be increased

  if(((gmuptr)m_currentChunk->m_curAddress + allocSize) > (gmuptr)m_currentChunk->m_lastAddress)
  {
    NewChunk();    
  }
  retPtr = m_currentChunk->m_curAddress;
  m_currentChunk->m_curAddress = (void*)((gmuptr)m_currentChunk->m_curAddress + allocSize);

  return retPtr;
}



void gmMemChain::Reset()
{
  m_currentChunk = m_rootChunk;
  if(m_currentChunk)
  {
    m_currentChunk->m_curAddress = m_currentChunk->m_minAddress;
  }
}



unsigned int gmMemChain::GetSystemMemUsed() const
{
  MemChunk * chunk = m_rootChunk;
  unsigned int total = 0;

  while(chunk)
  {
    total += m_chunkSize;
    chunk = chunk->m_nextChunk;
  }

  return total;
}

