/*
 *   Debugging implementation of MALLOC and friends
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "mem.h"

#if defined(DBG_MEMLEAKS)

typedef struct
{
  void *mem;
  char *allocated_in_func;
  char *allocated_in_file;
  unsigned int allocated_in_line;
}
MemDesc;


static int initialized = 0;
static int alloc_count = 0;
static MemDesc *alloc_list = NULL;


static void
dbg_memleaks_done (int exitcode, void *dummy)
{
  unsigned int i;

  (void) dummy;

  if (exitcode == 0 && alloc_count != 0) {
    fprintf (stderr, "\nmemory leak detected !!!\n");
    fprintf (stderr, "\nalloc_count == %i\n\n", alloc_count);
    for (i = 0; i < alloc_count; i++) {
      MemDesc *d = &alloc_list[i];

      fprintf (stderr, "chunk %p allocated in %s (%s: %u) not free'd !!\n",
          d->mem, d->allocated_in_func, d->allocated_in_file,
          d->allocated_in_line);
    }
    free (alloc_list);
  }
  fprintf (stderr, "\n");
}


static void
dbg_memleaks_init (void)
{
  on_exit (dbg_memleaks_done, NULL);
  initialized = 1;
}


void *
dbg_malloc (char *file, int line, char *func, size_t bytes)
{
  void *mem = (void *) malloc (bytes);
  MemDesc *d;

  if (!initialized)
    dbg_memleaks_init ();

  alloc_count++;
  alloc_list = realloc (alloc_list, alloc_count * sizeof (MemDesc));

  d = &alloc_list[alloc_count - 1];
  d->mem = mem;
  d->allocated_in_func = func;
  d->allocated_in_file = file;
  d->allocated_in_line = line;

  return mem;
}


void *
dbg_calloc (char *file, int line, char *func, size_t count, size_t bytes)
{
  void *mem = (void *) calloc (count, bytes);
  MemDesc *d;

  if (!initialized)
    dbg_memleaks_init ();

  alloc_count++;
  alloc_list = realloc (alloc_list, alloc_count * sizeof (MemDesc));

  d = &alloc_list[alloc_count - 1];
  d->mem = mem;
  d->allocated_in_func = func;
  d->allocated_in_file = file;
  d->allocated_in_line = line;

  return mem;
}


void *
dbg_realloc (char *file, int line, char *func, char *what,
    void *mem, size_t bytes)
{
  unsigned int i;

  for (i = 0; i < alloc_count; i++) {
    if (alloc_list[i].mem == mem) {
      alloc_list[i].mem = (void *) realloc (mem, bytes);
      return alloc_list[i].mem;
    }
  }

  if (mem != NULL) {
    fprintf (stderr,
        "%s: trying to reallocate unknown chunk %p (%s)\n"
        "          in %s (%s: %u) !!!\n",
        __FUNCTION__, mem, what, func, file, line);
    exit (-1);
  }

  return dbg_malloc (file, line, func, bytes);
}


void
dbg_free (char *file, int line, char *func, char *what, void *mem)
{
  unsigned int i;

  if (!initialized)
    dbg_memleaks_init ();

  for (i = 0; i < alloc_count; i++) {
    if (alloc_list[i].mem == mem) {
      free (mem);
      alloc_count--;
      memmove (&alloc_list[i], &alloc_list[i + 1],
          (alloc_count - i) * sizeof (MemDesc));
      return;
    }
  }

  fprintf (stderr, "%s: trying to free unknown chunk %p (%s)\n"
      "          in %s (%s: %u) !!!\n",
      __FUNCTION__, mem, what, func, file, line);
  exit (-1);
}


#endif