/* 
** memory.c
**
** useful functions to deal with some of the crap that goes
** on in amigados's memory management
**
** keep memory in a list
**
** no doubt, AmigaDOS does some of this for us, but we can't really
** debug that, or handle things like freeing all allocated resources
** (i.e. at end of a process) like we can doing things this way.
**
**  Note, also, relying on amigados means we risk disastrous results when 
**  things never allocated are freed.
**  Of course, the problem with this scheme is that if things overwrites its
**  memory and/or corrupts its list somehow, it can be really hosed. This 
**  has the perhaps stupidly optimistic assumption that there isn't much 
**  thrashing of memory going on.
*/

#define MEM_DEBUG

#ifdef MEM_DEBUG

#include <stdio.h>
#include <exec/memory.h>

#ifndef IFDEBUG
#ifdef DEBUG
#define IFDEBUG(foo) foo
#else
#define IFDEBUG(foo) 
#endif  /* DEBUG */
#endif  /* IFDEBUG */

void * AllocVec(unsigned long, unsigned long);

struct MemSeg {
   void * Addr;
   unsigned long Size;
};

/* here we store our data on memory allocated in a big array of 
MemSeg structures */

static struct MemSeg * AllocatedMemList = NULL;
static unsigned long MemListSize = 0;  /* space for mem entries in MemList */
static unsigned long NumMemEntries = 0;

/* allocate a larger space for AllocatedMemList, transfer data over to it */
ExpandAllocatedMemList(){
    struct MemSeg * NewMemList = NULL;
    unsigned long NewMemListSize = MemListSize + 50;
    unsigned long count;
    unsigned long countcheck = 0;

    IFDEBUG(printf("Expanding AllocatedMemList to %d\n", NewMemListSize);)

    /* get space for memory allocation records */
    NewMemList = AllocVec(sizeof(struct MemSeg) * NewMemListSize, MEMF_CLEAR);
    if(NewMemList == NULL){
    	/* this is way bad.  catastrophically die */
	fprintf(stderr, "ERROR: ExpandMemList(): out of memory!\n");
	return;
    }

    /* copy over the old data  from old space, if it is used */
    if(AllocatedMemList != NULL){
	IFDEBUG(printf("Transferring AllocatedMemList\n");)
	for(count=0,countcheck =0;count< MemListSize;count++){
	    if(AllocatedMemList[count].Addr != NULL){
		NewMemList[count].Addr = AllocatedMemList[count].Addr;
		NewMemList[count].Size = AllocatedMemList[count].Size;
		countcheck++;
	    } 
	}
	if(countcheck != NumMemEntries){
	    fprintf(stderr,"WARNING:Possible corruption of AllocatedMemList!\n");
	}
    }
    NumMemEntries = countcheck;

    if(AllocatedMemList != NULL ) FreeVec(AllocatedMemList);
    AllocatedMemList = NewMemList;
    MemListSize = NewMemListSize;

    IFDEBUG(printf("Done Expanding AllocatedMemList to %lx %d entries\n", AllocatedMemList, MemListSize);)
}

/* return pointer to MemSeg containing data for called pointer */
struct MemSeg * GetMemEntry(void * ptr){
    unsigned long count;

    IFDEBUG(printf("GetMemEntry(%lx)\n", ptr);)
    if(AllocatedMemList == NULL) {
	fprintf(stderr, "GetMemEntry() fail: no memlist\n");
    	return NULL;
    }

    for(count=0;count < MemListSize;count++){
    	if(AllocatedMemList[count].Addr == ptr) 
	    return &AllocatedMemList[count];
    }

    return NULL;
}

/* allocate a chunk of memory */
void * MemAlloc( size_t size , int flags ){
    void * NewAddr = AllocVec(size, flags);
    struct MemSeg * MemEntryData = NULL;

    IFDEBUG(printf("doing MemAlloc(%d)\n", size);)

    if(NewAddr == NULL) {
	fprintf(stderr, "MemAlloc fail -- no mem!\n");
    	return NULL;
    }

    if(AllocatedMemList == NULL || MemListSize <= NumMemEntries){
       ExpandAllocatedMemList();
    }

    MemEntryData = GetMemEntry( NULL ); /* find an unused slot in MemList */

    if(MemEntryData == NULL){
	/* Ack, this is bad. indicates corrupted AllocatedMemList 
	** or an out of memory situation (couldn't expand Allocated list )
	*/
	FreeVec(NewAddr);
	fprintf(stderr, "Error:MemAlloc fail: no MemEntry\n");
	return NULL;
    }

    MemEntryData->Addr  = NewAddr;
    MemEntryData->Size  = size;
    NumMemEntries++;

    IFDEBUG(printf("done MemAlloc(%d)\n", size);)
    return NewAddr;
}

/* 
**  free a chunk of memory 
**
**  Make up some warning if wasn't allocated, or we're freeing up the wrong
**  size.
**
*/
MemFree(void * ptr, unsigned long size ){
   unsigned long count;
   struct MemSeg * memptr;

   IFDEBUG(printf("doing MemFree(%d %d )\n", ptr, size);)

   if(AllocatedMemList == NULL) {
       fprintf(stderr, "ERROR: MemFree'd before any allocation!\n");
       return;
   }

   memptr = GetMemEntry(ptr);

   if(memptr == NULL){
      fprintf(stderr, "WARNING: tried to free unallocated pointer, sz %d!\n", 
      	size);
      return;
   }

   if(memptr->Addr != ptr){
      fprintf(stderr, "ERROR: GetMemEntry failed!  got wrong memptr!\n");
   }

   if(size > 0 && memptr->Size != size){
      fprintf(stderr, 
      	"Warning: allocated size (%d) and  freed size (%d) disagree\n", 
	memptr->Size, size);
   }

   FreeVec(ptr);
   memptr->Size = 0;
   memptr->Addr = NULL;
   NumMemEntries--;

   /* look for multiple entries? */
    if(ptr != 0)
	while((memptr = GetMemEntry(ptr)) != NULL){
		fprintf(stderr, 
		    "WARNING: multiply allocated memory? size (%d)\n", 
		    memptr->Size);
		memptr->Addr = NULL;
		memptr->Size = 0;
	}
    IFDEBUG(printf("Done MemFree\n");)
}

/* free up anything that was left allocated */
AllFree(){
    unsigned long count;
    unsigned long countcheck = NumMemEntries;

    IFDEBUG(puts("doing AllFree");)

    if(NumMemEntries != 0 )
	fprintf(stderr, "AllFree(): %d entries to free\n", NumMemEntries);

    if(AllocatedMemList == NULL){
	fprintf(stderr, "No memory list to AllFree!\n");
	return;
    }

    for(count = 0;count < MemListSize ;count++){
       if(AllocatedMemList[count].Addr != NULL){
           fprintf(stderr,"Block %d bytes to free at %lx\n", 
	   	AllocatedMemList[count].Size, AllocatedMemList[count].Addr);
           FreeVec(AllocatedMemList[count].Addr);
	   countcheck--;
       }
    }

    if(countcheck != 0){
       fprintf(stderr, "AllClean: %d unaccounted memory allocations!\n", countcheck);
    } 
    IFDEBUG(else fprintf(stderr, "AllClean: no unaccounted memory allocations!\n");)

    FreeVec(AllocatedMemList);
    AllocatedMemList = NULL;

    IFDEBUG(fprintf(stderr, "AllFree done\n");)
}


/* function definitions to cause things here to replace ordinary function 
** calls */

void * AllocMem(size_t size, unsigned long flags){
   return MemAlloc( size, flags);
}
void FreeMem( void * ptr, size_t size){
   MemFree(ptr, size);
}

void * malloc(size_t size){
   return MemAlloc( size, MEMF_CLEAR);
}
void * calloc(int nmemb, size_t size){
   return MemAlloc( nmemb * size, MEMF_CLEAR);
}
void free(void * ptr){
   MemFree(ptr, 0);
}

char * strdup( char * string){
   int lenstr;
   char * newptr;
   if(string == NULL) return;

   lenstr = strlen(string)+1;  /* plus 1 for null terminator! */

   newptr = (char *) malloc( lenstr );

   if(newptr != NULL) strcpy(newptr, string);

   return newptr;
}

/* have to macro define AllocVec/FreeVec, as they're used in this! */

#endif
