#include <string.h>
#include "memleak.h"

#ifndef NDEBUG


/*
Linklist based on Desk[Lib]'s, could be replaced by macros if linked with such.
*/
struct memleak__link {
  struct memleak__link *previous;
  struct memleak__link *next;
};
typedef struct memleak__link memleak_link;

static void memleak_unlink(memleak_link *anchor, memleak_link *item)
{
  memleak_link *prev, *next;

  next = item->next;
  prev = item->previous;

  if (next == 0)
    anchor->previous = prev;
  else
    next->previous   = prev;

  if (prev == 0)
    anchor->next = next;
  else
    prev->next   = next;

  item->next     = 0;
  item->previous = 0;
}

static void memleak_add_link(memleak_link *anchor, memleak_link *item)
{
  memleak_link *oldlast;

  oldlast          = anchor->previous;
  item->next       = 0;
  item->previous   = oldlast;

  if (oldlast == 0)
    anchor->next = item;
  else
    oldlast->next = item;
  anchor->previous = item;
}

static void memleak_insert_after(memleak_link *anchor,
	memleak_link *pos, memleak_link *item)
{
  memleak_link *oldnext;

  oldnext = pos->next;

  if (oldnext == 0)
    memleak_add_link(anchor, item);
  else
  {
    oldnext->previous = item;
    item->next        = oldnext;

    pos->next         = item;
    item->previous    = pos;
  }
}

static int memleak_in_list(memleak_link *anchor, memleak_link *item)
{
  memleak_link *ptr;

  ptr = anchor->next;
  while (ptr != 0)
  {
    if (ptr == item)
      return 1;

    ptr = ptr->next;
  }

  return 0;
}

/******************************************/

typedef struct {
  memleak_link link;
  size_t size;
  int line;
} memleak_header;

#define memleak_MEMBER(p, m) (((memleak_header *) (p))->m)
#define memleak_LINE(p) memleak_MEMBER(p, line)
#define memleak_SIZE(p) memleak_MEMBER(p, size)
#define memleak_FILE(p) \
	((char *) (p) + sizeof(memleak_header) + memleak_SIZE((p)))

static memleak_link memleak_list = {0, 0};
static FILE *memleak_stream = stderr;
static int memleak__verbose = 0;
static int memleak_initialised = 0;

static void memleak_report()
{
  memleak_link *link;

  fprintf(memleak_stream, "\nMemLeak blocks left unfreed at exit:\n");
  for (link = memleak_list.next; link; link = link->next)
  {
    fprintf(memleak_stream, "%p (%p) of %d bytes allocated by %s : %d\n",
    	(memleak_header *) link + 1, link, memleak_SIZE(link),
    	memleak_FILE(link), memleak_LINE(link));
  }
  putc('\n', memleak_stream);
  fclose(memleak_stream);
}

static int memleak_check(void *p, const char *fn)
{
  if (!memleak_in_list(&memleak_list, (memleak_link *) p))
  {
    fprintf(stderr, "%p not a valid MemLeak block (%s)\n", p, fn);
    fprintf(memleak_stream, "%p not a valid MemLeak block (%s)\n", p, fn);
    return 0;
  }
  return 1;
}

memleak_t memleak_realloc(void *p, size_t size, const char *file, int line, int zero)
{
  memleak_t np;
  memleak_link *prev = 0;

  if (!memleak_initialised)
  {
    atexit(memleak_report);
    memleak_initialised = 1;
    if (memleak__verbose)
      fprintf(memleak_stream, "\nMemLeak system initialised\n");
  }
  if (memleak__verbose)
    putc('\n', memleak_stream);
  if (p)
  {
    if (memleak__verbose)
      fprintf(memleak_stream, "MemLeak realloc of %p (%p) by %s : %d\n",
      	p, (memleak_header *) p - 1, file,line);
    p = (void *) ((memleak_header *) p - 1);
    if (!memleak_check(p, "realloc"))
      return 0;
    if (memleak__verbose)
      fprintf(memleak_stream, "Last allocated at %d by %s : %d\n",
      	memleak_SIZE(p), memleak_FILE(p), memleak_LINE(p));
    prev = ((memleak_link *) p)->previous;
    memleak_unlink(&memleak_list, (memleak_link *) p);
  }
  if (memleak__verbose)
    fprintf(memleak_stream, "Memleak alloc of %d bytes by %s : %d\n",
    	size, file, line);
  np = (realloc)(p, size + sizeof(memleak_header) + strlen(file) + 1);
  if (!np)
  {
    if (memleak__verbose)
      fprintf(memleak_stream, "Free pool exhausted\n\n");
    if (p)
      memleak_insert_after(&memleak_list, prev, (memleak_link *) p);
    return 0;
  }
  memleak_add_link(&memleak_list, (memleak_link *) np);
  memleak_SIZE(np) = size;
  memleak_LINE(np) = line;
  strcpy(memleak_FILE(np), file);
  if (memleak__verbose)
    fprintf(memleak_stream, "Allocated at %p (%p)\n\n",
    	(memleak_header *) np + 1, np);
  if (zero)
    memset((memleak_t) ((memleak_header *) np + 1),0,size);
  return (memleak_t) ((memleak_header *) np + 1);
}

void memleak_free(void *p, const char *file, int line)
{
  if (memleak__verbose)
    fprintf(memleak_stream, "\nMemLeak free of %p (%p) by %s : %d\n",
    	p, (memleak_header *) p - 1, file, line);
  if (!memleak_initialised)
  {
    fprintf(stderr, "\nMemLeak system not initialised\n");
    return;
  }
  p = (void *) ((memleak_header *) p - 1);
  if (!memleak_check(p, "free"))
    return;
  if (memleak__verbose)
    fprintf(memleak_stream, "Allocated at %d by %s : %d\n",
    	memleak_SIZE(p), memleak_FILE(p), memleak_LINE(p));
  memleak_unlink(&memleak_list, (memleak_link *) p);
  (free)(p);
}

void memleak_set_stream(FILE *fp)
{
  memleak_stream = fp;
}

void memleak_verbose(int v)
{
  memleak__verbose = v;
}

#endif
