#include "MemoryManager.h"

inline void ManageMatrix(MemoryManager* mgr, matrix* m)
{
  matrix **matrices;
  if(mgr->nextm == mgr->lengthm)
  {
    if (! (matrices = realloc(mgr->matrices, sizeof(matrix*) * (mgr->lengthm + mgr->addressblockm))))
    {
      merror(0, "Out of memory");
      return;
    }
    mgr->matrices = matrices;
    mgr->lengthm += mgr->addressblockm;    
  }
  mgr->matrices[mgr->nextm++] = m;
}

inline void ManageMemory(MemoryManager* mgr, void* mem)
{
	void** memory;
	if (mgr->next == mgr->length)
	{
		if (! (memory = realloc(mgr->memory, sizeof(void*) * (mgr->length + mgr->addressblock))))
		{
			merror(0, "Out of memory");
			return;
		}
		mgr->memory = memory;
		mgr->length += mgr->addressblock;
	}
	mgr->memory[mgr->next++] = mem;
}
  
inline void* mmalloc(MemoryManager* mgr, size_t nbytes)
{
  void **memory;
  if(mgr->next == mgr->length)
  {
    if (! (memory = realloc(mgr->memory, sizeof(void*) * (mgr->length + mgr->addressblock))))
    {
      merror(0, "Out of memory");
      return;
    }
    mgr->memory = memory;
    mgr->length += mgr->addressblock;    
  }  
  return mgr->memory[mgr->next++] = tmalloc(nbytes);
} 

inline void* mcalloc(MemoryManager* mgr, size_t n, size_t nbytes)
{
  void **memory;
  if(mgr->next == mgr->length)
  {
    if (! (memory = realloc(mgr->memory, sizeof(void*) * (mgr->length + mgr->addressblock))))
    {
      merror(0, "Out of memory");
      return;
    }
    mgr->memory = memory;
    mgr->length += mgr->addressblock;    
  }  
  return mgr->memory[mgr->next++] = tcalloc(n, nbytes);
}

inline void MAllocateMatrixType(MemoryManager* mgr, int type, matrix* m, int rows, int cols)
{
  AllocateMatrixType(type, m, rows, cols);
  if (rows || cols)
  {
    ManageMatrix(mgr, m);
  }
}

void FreeMemory(MemoryManager* mgr)
{
  int i;
  for (i = 0; i < mgr->nextm; i++)
  {
    if (mgr->matrices[i]->type) DeallocateMatrix(mgr->matrices[i]);
  }
  free(mgr->matrices);
  mgr->matrices = 0;
  for (i = 0; i < mgr->next; i++)
  {
    free(mgr->memory[i]);
  }
  free(mgr->memory);
  mgr->memory = 0;
}

MemoryManager CreateMemoryManager(int InitialLength, int InitialMatrices, int AddressBlockSize, int MatrixAddressBlockSize)
{
  MemoryManager mgr;
  mgr.next = 0;
  mgr.nextm = 0;
  if (InitialLength <= 0) InitialLength = 1;
  if (InitialMatrices <= 0) InitialMatrices = 1;
  if (AddressBlockSize <= 5) AddressBlockSize = 5;
  if (MatrixAddressBlockSize <= 5) MatrixAddressBlockSize = 5;
  mgr.length = InitialLength;
  mgr.lengthm = InitialMatrices;
  mgr.memory = tcalloc(InitialLength, sizeof(void*));
  mgr.matrices = tcalloc(InitialMatrices, sizeof(void*));
  mgr.addressblock = AddressBlockSize;
  mgr.addressblockm = MatrixAddressBlockSize;
  return mgr;
}

/*CreateMemoryGrower initializes a memory grower structure.*/
MemoryGrower CreateMemoryGrower(int pointers, int blocksize)
{
	MemoryGrower g;
	if (pointers < 1 || blocksize < 1)
	{
		merror(0, "CREATEMEMORYGROWER: Parameters are non-positive");
		memset(&g, 0, sizeof(MemoryGrower));
		return g;
	}
	
	g.blocksize = blocksize;
	g.memories = pointers;
	g.memory = tcalloc(pointers, sizeof(void*));
	g.capacity = tcalloc(pointers, sizeof(int));
	g.next = 0;
	return g;
}

void* growMalloc(MemoryGrower* g, void* memory, int size)
{
	/*If the pointer is null, allocate some new memory.*/
	if (! g)
	{
		merror(0, "MEMORYGROWER: The pointer to the grower is null");
		return 0;
	}
	if (! memory)
	{
		if (g->next == g->memories)
		{
			merror(0, "MEMORYGROWER: Too many pointers registered with the grower");
			return 0;
		}
		if (size < g->blocksize) size = g->blocksize;
		g->memory[g->next] = tmalloc(size);
		g->capacity[g->next] = size;
		return g->memory[g->next++];
	}
	else
	{
		/*Otherwise, find the index of the memory record.*/
		int i;
		for (i = 0; i < g->next; i++)
		{
			if (memory == g->memory[i]) break;
		}
		if (i == g->next)
		{
			merror(0, "MEMORYGROWER: The memory did not originate with the grower");
			return 0;
		}
		/*If the request size is smaller than the current capacity return without
		reallocating.*/
		if (size < g->capacity[i]) 
		{
			return memory;
		}
		/*Otherwise, allocate as necessary.*/
		{
			if (size < g->capacity[i] + g->blocksize) size = g->capacity[i] + g->blocksize;
			void* newMem = tmalloc(size);
			memcpy(newMem, memory, g->capacity[i]);
			g->capacity[i] = size;
			g->memory[i] = newMem;
			free(memory);
			return newMem;
		}
	}
}

void* growCalloc(MemoryGrower* g, void* memory, int elementSize, int elements)
{
	return growMalloc(g, memory, elementSize * elements);
}


/*FreeMemoryGrower deallocates all the memories pointed to by a memory
grower.*/
void FreeMemoryGrower(MemoryGrower* g)
{
	if (! g) return;
	int i;
	for (i = 0; i < g->next; i++)
	{
		free(g->memory[i]);
	}
	free(g->memory);
	free(g->capacity);
	memset(g, 0, sizeof(MemoryGrower));
}
