/* Memory.c: geheugenbeheer met debugmogelijkheden. */

#include <stdlib.h>
#include <string.h>
#include "Memory.h"
#include "error.h"
#include "debug.h"

/* totaal aantal bytes gealloceerd geheugen (met deze routines!! */
static int bytesAllocated=0;

int GetMemoryUsage(void)
{
	return bytesAllocated;
}

/* alloceer nrbytes bytes en geef een wijzer naar het gealoceerde geheugen 
 * terug */
char *Alloc(int nrbytes)
{
	char *buf;

	DPRINTF(stderr, "Alloc %d bytes ... ", nrbytes);
	if ((buf = (char *)malloc(nrbytes)) == NULL) 
		Fatal(1, "Alloc", "Ik kan geen %d bytes meer alloceren", nrbytes);
	bytesAllocated += nrbytes;
	DPRINTF(stderr, "OK: in het totaal %d bytes gealloceerd nu.\n", bytesAllocated);
	
	return buf;
}

/* geef de nrbytes bytes geheugen waar buf een wijzer nar is terug vrij
 * (tweede argument is enkel nodig voor het bijwerken van het totaal
 * aantal bytes gealloceerd geheugen */
void Free(char *buf, int nrbytes)
{
	DPRINTF(stderr, "Free %d bytes ... ", nrbytes);
	free(buf);
	bytesAllocated -= nrbytes;
	DPRINTF(stderr, "OK: in het totaal %d bytes gealloceerd nu.\n", bytesAllocated);
}

/* maar de geheugenzone van size bytes, waar buf een wijzer naar is
 * extra bytes langer (of korter als extra negatief is). De nieuwe
 * lengte is size+extra */
char *Realloc(char *buf, int size, int extra)
{
	DPRINTF(stderr, "Realloc: %d bytes -> %d bytes ... ", size, size+extra);
	
	if ((buf = (char *)realloc(buf, size+extra)) == NULL) 
		Fatal(1, "Realloc", "Ik kan geen %d bytes meer realloceren", 
		                    extra);
	bytesAllocated += extra;
	DPRINTF(stderr, "OK: in het totaal %d bytes gealloceerd nu.\n", bytesAllocated);
	
	return buf;
}

/* ----------------------------------------------------------------- *
 * Doel van de volgende routines: overhead (zowel in tijd als in 
 * geheugenruimte) van de standaard C run-time storage allocator 
 * vermijden door gebruik te maken van het feit dat we in dit programma
 * erg veel kleine stukjes geheugen van allemaal dezelfde grootte
 * nodig hebben. 
 *
 * Hoe opgelost? Door die kleine stukjes geheugen samen in "pagina's"
 * te groeperen van enkele KB groot. Iedere pagina bevat naast een
 * aantal gegevens over de mate waarin ze gevuld is, een bitmap
 * met een bit per cel in de pagina die 0 is als de cel vrij is en 1 indien
 * bezet. Voor ieder celtype wordt een dubbel gelinkte lijst van
 * dergelijke pagina's bijgehouden. Pagina's worden meteen uit die
 * lijst verwijderd indien ze leeg zijn. De pagina's hebben een vaste
 * grootte ongeacht het celtype (bepaald door cellsize).
 *
 * Indien gecompileerd met #define MEMMAN_HEADERS, wordt iedere
 * cel voorafgegaan door een hoofding waarin een pointer naar de pagina
 * waartoe de cel behoort: dit maakt het vrijgeven van cellen aanzienlijk
 * sneller maar veroorzaakt aan de andere kant natuurlijk ook een overhead
 * van 4 bytes per cel. 
 */

/* beperking op het afzoeken van pagina's naar vrije ruimte */
#define MAX_SEARCH_PAGES 500

static STORAGE *FindFreePage(STORAGE *stor)
{
	int count = 0;

	while (stor != (STORAGE *)NULL && count < MAX_SEARCH_PAGES) {
		if (stor->cellsfilled < stor->maxcells) 
			return stor;
		stor = stor->next;
		count++;
	}

	return (STORAGE *)NULL;
}

static STORAGE *NewPage(unsigned int cellsize)
{
	STORAGE *newpage;

	if (cellsize < MINCELLSIZE) 
		Fatal(-3, "NewPage", "Cell size (%d bytes) too small", cellsize);

	newpage = (STORAGE *)Alloc(sizeof(STORAGE));
#ifdef MEMMAN_HEADERS
	newpage->maxcells = PAGESIZE/(HEADERSIZE + cellsize);
#else
	newpage->maxcells = PAGESIZE/cellsize;
#endif
	newpage->cellsfilled = 0;
	newpage->cellsize = cellsize;
	newpage->nextfree = 0;

	memset(newpage->allocmap, 0, ALLOCMAPSIZE);

	return newpage;
}

/* the new page is attached in front of the previoud\sly allocated ones.
 * A pointer to the new chain is returned. */
static STORAGE *AttachPage(STORAGE *page, STORAGE *stor)
{
	if (stor) stor->prev = page;
	page->next = stor;
	page->prev = (STORAGE *)NULL;

	return page;
}

static STORAGE *ReleasePage(STORAGE *page, STORAGE *stor)
{
	if (page == stor)	/* first page in chain */
		stor = page->next;
	else
		page->prev->next = page->next;

	if (page->next) page->next->prev = page->prev;

	Free((char *)page, sizeof(STORAGE));

	return stor;
}

#define MARK_ALLOCED(allocmap,i)   (allocmap)[(i)>>3] |= (1 << ((i)&7))
#define MARK_FREED(allocmap,i)   (allocmap)[(i)>>3] &= ~(1 << ((i)&7))
#define MARKED_ALLOCED(allocmap,i)   (allocmap)[(i)>>3] & (1 << ((i)&7))

#define INCREMENT_MODULO(count,base)  {(count)++; if ((count) >= (base)) (count) = 0;}

static unsigned char *AllocCell(STORAGE *page)
{
	unsigned char *cellptr;

#ifdef MEMMAN_HEADERS
	cellptr = page->base + page->nextfree * (HEADERSIZE + page->cellsize);
	((CELLHDR *)cellptr)->page = page;
	cellptr += HEADERSIZE;
#else
	cellptr = page->base + page->nextfree * page->cellsize;
#endif
	MARK_ALLOCED(page->allocmap, page->nextfree);
	page->cellsfilled ++;

/* find next free cell */
	if (page->cellsfilled < page->maxcells) {
		INCREMENT_MODULO(page->nextfree, page->maxcells);
		while (MARKED_ALLOCED(page->allocmap, page->nextfree)) 
			INCREMENT_MODULO(page->nextfree, page->maxcells);
	}

	return cellptr;
}

unsigned char *New(unsigned int cellsize, STORAGE **stor)
{
	STORAGE *page;

	if ((page = FindFreePage(*stor)) == (STORAGE *)NULL) {
		page = NewPage(cellsize);
		*stor = AttachPage(page, *stor);
	}

	return AllocCell(page);
}

#ifndef MEMMAN_HEADERS
static STORAGE *FindWhichPage(unsigned char *ptr, STORAGE *stor)
{
	while (stor != (STORAGE *)NULL) {
		if (ptr >= stor->base && ptr < stor->base+PAGESIZE)
			return stor;
		stor = stor->next;
	}

	Fatal(-3, "FindWhichPage", "Trying to free non previously allocated storage");
	return (STORAGE *)NULL;	      
}
#endif /*MEMMAN_HEADERS*/

void Dispose(unsigned char *cellptr, STORAGE **stor)
{
	STORAGE *page;

#ifdef MEMMAN_HEADERS
	cellptr -= HEADERSIZE;
	page = ((CELLHDR *)cellptr)->page;
	page->nextfree = (cellptr - page->base)/(page->cellsize + HEADERSIZE);
#else
	page = FindWhichPage(cellptr, *stor);
	page->nextfree = (cellptr - page->base)/page->cellsize;
#endif

	MARK_FREED(page->allocmap, page->nextfree);
	page->cellsfilled --;

	if (page->cellsfilled <= 0)
		*stor = ReleasePage(page, *stor);
}


