/*
    Module : MemTrace

    Purpose: All memory (re)allocations take place here. Keeps it tidy.

    Version: 0.01

    Date   : 11th July 2005

    Started: 11th July 2005

    Author : Rick Murray
             http://www.heyrick.co.uk/eurovision/

*/


#include "EuroScore.h"


/* ALLOW THE FOLLOWING DEFINITION TO WRITE MEMTRACE INFORMATION TO STDERR
   (you can't turn off the unreleased claim output - it's there for a REASON) */
// #define MTDEBUG 1

typedef struct mtdef
{
   int  address;
   int  size;
   char allocmethod[1];
   char parentfunc[23];
};
static struct mtdef *memtrace = NULL;
static int  mtcount = 0;

#define MTMAX 32
#define CALLOC 1
#define MALLOC 2
#define REALLOC 3


void memtrace_init(void);
void memtrace_finish(void);
void memtrace_addentry(int base, int size, int method, char *parent);
void memtrace_removeentry(int base);
void *memtrace_calloc(size_t nmemb, size_t size);
void *memtrace_namedcalloc(size_t nmemb, size_t size, char *parent);
void memtrace_free(void *ptr);
void *memtrace_malloc(size_t size);
void *memtrace_namedmalloc(size_t size, char *parent);
void *memtrace_realloc(void *ptr, size_t size);
void *memtrace_namedrealloc(void *ptr, size_t size, char *parent);
void memtrace_checkclaims(FILE *stream);
void memtrace_dumpsixtyfour(int address, FILE *stream);


void memtrace_init(void)
{
   /* Sets up memtrace */
   memtrace = calloc(MTMAX, sizeof(struct mtdef));
   if (memtrace == NULL)
   {
      fprintf(stderr, "Couldn't perform memtrace_init, out of memory.\n\n");
      exit(EXIT_FAILURE);
   }

   mtcount = 0;

   atexit(memtrace_finish);

   return;
}



void memtrace_finish(void)
{
   if (memtrace == NULL)
   {
      fprintf(stderr, "memtrace_finish() called while 'memtrace' struct NULL - is it being called twice?\n\n");
      exit(EXIT_FAILURE);
   }

   memtrace_checkclaims(stderr);

   /* Now free it */
   if (memtrace != NULL)
   {
      free(memtrace);
      memtrace = NULL;
   }

   return;
}



void memtrace_addentry(int base, int size, int method, char *parent)
{
   int  slot = -1;
   int  loop = 0;

#ifdef MTDEBUG
   fprintf(stderr, "DEBUG: memtrace_addentry()\n");
#endif

   /* Try to find an unused slot */
   loop = 0;
   while ( (slot == -1) && (loop < MTMAX) )
   {
#ifdef MTDEBUG
      fprintf(stderr, "DEBUG:  Checking slot %d : %d\n", loop, memtrace[loop].address);
#endif
      if (memtrace[loop].address == 0)
      {
#ifdef MTDEBUG
         fprintf(stderr, "DEBUG:  Slot %d is available.\n", loop);
#endif
         slot = loop;
      }
      loop++;
   }

   /* If not found, make a new one */
   if (slot == -1)
   {
      mtcount++;
      slot = mtcount;

#ifdef MTDEBUG
      fprintf(stderr, "DEBUG:  No slot, adding another slot, slots now %d.\n", slot);
#endif

      if (mtcount >= MTMAX)
      {
         fprintf(stderr, "Too many claims for memtrace to follow.\n\n");
         exit(EXIT_FAILURE); // may cause lots of unresolved claims
      }
   }

   /* Set up base, size, method, and owner */
   memtrace[slot].address        = base;
   memtrace[slot].size           = size;
   memtrace[slot].allocmethod[0] = method;
   strcpy(memtrace[slot].parentfunc, parent);

#ifdef MTDEBUG
   fprintf(stderr, "DEBUG: Added entry at &%08X (slot %d) from %s\n", \
           base, slot, memtrace[slot].parentfunc);
#endif

   return;
}



void memtrace_removeentry(int base)
{
   int  loop = 0;
   int  slot = -1;

   /* Find the entry */
   for (loop = 0; loop < MTMAX; loop++)
   {
      if (memtrace[loop].address == base)
         slot = loop;
   }

   if (slot == -1)
   {
      /* It isn't one we know of! */
      fprintf(stderr, "memtrace_removeentry() called with address &%08X, unknown claim!\n(memory dump of area follows)\n", \
              base);
      memtrace_dumpsixtyfour(base, stderr);

#ifdef MTDEBUG
      fprintf(stderr, "DEBUG: memtrace_removeentry() called with address &%08X, UNKNOWN!\n", \
              base);
      memtrace_dumpsixtyfour(base, stderr);
#endif
      exit(1);
   }

#ifdef MTDEBUG
   fprintf(stderr, "DEBUG: Removing entry at address &%08X (slot %d)\n", base, slot);
#endif

   memtrace[slot].address        = 0;
   memtrace[slot].size           = 0;
   memtrace[slot].allocmethod[0] = 0;

   /* Reduce the count, if this was the last item */
   if (slot == mtcount)
      mtcount--;

   /* We don't bother to garbage collect (ie, if freeing item 7 and the previous 6 are free
      we might consider reducing mtcount to zero, but we won't...) because the allocator will
      look for blanks to fill FIRST. */

   return;
}



void *memtrace_calloc(size_t nmemb, size_t size)
{
   /* This looks like "calloc()" */

   void *ptr;
   int  extent;

   ptr = calloc(nmemb, size);

   extent = (int)nmemb * (int)size;

   memtrace_addentry( (int)ptr, extent, CALLOC, "");

   return ptr;
}



void *memtrace_namedcalloc(size_t nmemb, size_t size, char *parent)
{
   /* This looks like "calloc()", but includes "parent" function pointer */

   void *ptr;
   int  extent;

   ptr = calloc(nmemb, size);

   extent = (int)nmemb * (int)size;

   memtrace_addentry( (int)ptr, extent, CALLOC, parent);

   return ptr;
}



void memtrace_free(void *ptr)
{
   /* This looks like "free()" */

   free(ptr);

   memtrace_removeentry( (int)ptr );

   return;
}



void *memtrace_malloc(size_t size)
{
   /* This looks like "malloc()" */

   void *ptr;

   ptr = malloc(size);

   memtrace_addentry( (int)ptr, (int)size, MALLOC, "");

   return ptr;
}



void *memtrace_namedmalloc(size_t size, char *parent)
{
   /* This looks like "malloc()", but includes "parent" function pointer */

   void *ptr;

   ptr = malloc(size);

   memtrace_addentry( (int)ptr, (int)size, MALLOC, parent);

   return ptr;
}



void *memtrace_realloc(void *ptr, size_t size)
{
   /* This looks like "realloc()" */

   void *newptr;

   newptr = realloc(ptr, size);

   if (newptr != NULL)
   {
      /* Only update it if it was updated... */
      if (ptr != NULL)
         memtrace_removeentry( (int)ptr ); /* Only if non-null, else we malloc'd */
      memtrace_addentry( (int)newptr, (int)size, REALLOC, "");
   }

   return newptr;
}



void *memtrace_namedrealloc(void *ptr, size_t size, char *parent)
{
   /* This looks like "realloc()", but includes "parent" function pointer */

   void *newptr;

   newptr = realloc(ptr, size);

   if (newptr != NULL)
   {
      /* Only update it if it was updated... */
      if (ptr != NULL)
         memtrace_removeentry( (int)ptr ); /* Only if non-null, else we malloc'd */
      memtrace_addentry( (int)newptr, (int)size, REALLOC, parent);
   }

   return newptr;
}



void memtrace_checkclaims(FILE *stream)
{
   int  loop = 0;
   int  first = TRUE;

   for (loop = 0; loop < MTMAX; loop++)
   {
      if ( memtrace[loop].address != 0 )
      {
         if (first)
         {
            fprintf(stream, "memtrace claims not released:\n=============================\n\n");
            first = FALSE;
         }

         fprintf(stream, "Claim entry %d:\n", loop);
         fprintf(stream, "  Base address : &%08X\n  Area extent  : %d bytes\n  Claimed with : ",\
                 memtrace[loop].address, memtrace[loop].size);
         switch (memtrace[loop].allocmethod[0])
         {
            case 0 : fprintf(stream, "!unknown!\n");
                     break;
            case 1 : fprintf(stream, "calloc()\n");
                     break;
            case 2 : fprintf(stream, "malloc()\n");
                     break;
            case 3 : fprintf(stream, "realloc()\n");
                     break;
            default: fprintf(stream, "!error - method %d!\n", memtrace[loop].allocmethod[0]);
                     break;
         }
         fprintf(stream, "  Claimed by   : %s\n\n", memtrace[loop].parentfunc);

         memtrace_dumpsixtyfour(memtrace[loop].address, stream);
         fprintf(stream, "\n\n");

      }
   }

   return;
}



void memtrace_dumpsixtyfour(int address, FILE *stream)
{
   char *offset = NULL;
   int  byte = 0;
   int  prloopouter = 0;
   int  prloopinner = 0;

   offset = (char *)address;

   fprintf(stream, "Address  : 00 01 02 03 04 05 06 07 : ASCII data\n");
   for (prloopouter = 0; prloopouter < 8; prloopouter++)
   {
      fprintf(stream, "%08X : ", (int)offset);
      for (prloopinner = 0; prloopinner < 8; prloopinner++)
      {
         byte = offset[prloopinner];
         fprintf(stream, "%02X ", byte);
      }
      fprintf(stream, ": ");
      for (prloopinner = 0; prloopinner < 8; prloopinner++)
      {
         byte = offset[prloopinner];
         if ( (byte < 32) || (byte == 127) )
            byte = '.';
         fprintf(stream, "%c", byte);
      }
      fprintf(stream, "\n");
      offset += 8;
   }

   return;
}