/* This file was contributed by Peter Selinger and is copyrighted
   under the GNU General Public License. */

/* The purpose of this file is to define alternative "malloc",
   "realloc", and "free" macros (together with some friends, such as
   "strdup"), for the purpose of detecting memory leaks. This is a
   debugging tool, and is disabled by default. */

/* this code monitors *other* code's memory usage, not its own! */

#define _GNU_SOURCE /* needed for stdio.h:vasprintf */
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>

#include "lists.h"
#include "alias.h"

struct memwatch_s {
  char *file;
  int line;
  struct timeval tv;
  void *p;
  size_t size;
  int tag;
  struct memwatch_s *next;
};
typedef struct memwatch_s memwatch_t;

memwatch_t *memlist=NULL;

void memlog(char *fmt, ...) {
  va_list args;
  char buf[100];
  FILE *f;

  va_start(args, fmt);
  vsprintf(buf, fmt, args);
  va_end(args);

  f = fopen("memory.log", "a");
  if (f) {
    fputs(buf, f);
    fclose(f);
  }
}

char *ttoa(struct timeval *tv) {
  static char buf[100];

  sprintf(buf, "%lds+%6ldms", tv->tv_sec, tv->tv_usec);
  return buf;
}  

void *memwatch_malloc(size_t size, char *file, int line) {
  void *p;
  memwatch_t *m;
  struct timeval tv;

  gettimeofday(&tv, NULL);

  p = malloc(size);
  
  if (!p) {
    memlog("%s:%d: malloc(%d) returned 0 (%s)\n", file, line, size, ttoa(&tv));
    return NULL;
  }

  m = malloc(sizeof(memwatch_t));

  m->file = file;
  m->line = line;
  m->tv.tv_sec = tv.tv_sec;
  m->tv.tv_usec = tv.tv_usec;
  m->p = p;
  m->size = size;
  m->tag = 0;

  list_prepend(memlist, m);

  if (nvar_default("memwatchverbose",0)) {
    memlog("%s:%d: malloc(%d) returned %p (%s)\n", file, line, size, p, ttoa(&tv));
  }

  return p;
}

void memwatch_free(void *p, char *file, int line) {
  memwatch_t *m;
  memwatch_t **hook;
  struct timeval tv;

  if (p!=NULL) {

    gettimeofday(&tv, NULL);
    
    list_find2(m, memlist, m->p == p, hook);
    
    if (m==NULL) {
      memlog("%s:%d: bad free(%p) (%s)\n", file, line, p, ttoa(&tv));
      return;
    }
    
    list_unlink_athook(memlist, m, hook);
    
    free(m);
    free(p);

    if (nvar_default("memwatchverbose",0)) {
      memlog("%s:%d: free(%p) (%s)\n", file, line, p, ttoa(&tv));
    }

  }
}

void *memwatch_realloc(void *p, size_t size, char *file, int line) {
  void *q;
  memwatch_t *m;
  memwatch_t **hook;
  struct timeval tv;

  gettimeofday(&tv, NULL);

  if (p!=NULL)
  {
    list_find2(m, memlist, m->p == p, hook);

    if (m==NULL) {
      memlog("%s:%d: bad realloc(%p, %d) (%s)\n", file, line, p, size, ttoa(&tv));
      return NULL;
    }

    list_unlink_athook(memlist, m, hook);
  } 
  else 
  {
    m = malloc(sizeof(memwatch_t));
  }
    
  q = realloc(p, size);

  if (!q) {
    memlog("%s:%d: realloc(%p, %d) returned 0 (%s)\n", file, line, p, size, ttoa(&tv));
    return NULL;
  }

  m->file = file;
  m->line = line;
  m->tv.tv_sec = tv.tv_sec;
  m->tv.tv_usec = tv.tv_usec;
  m->p = q;
  m->size = size;
  m->tag = 0;

  list_prepend(memlist, m);

  if (nvar_default("memwatchverbose",0)) {
    memlog("%s:%d: realloc(%p, %d) returned %p (%s)\n", file, line, p, size, q, ttoa(&tv));
  }
  return q;
}  

char *memwatch_strdup(const char *s, char *file, int line) {
  char *p;
  memwatch_t *m;
  struct timeval tv;

  gettimeofday(&tv, NULL);

  if (!s) {
    memlog("%s:%d: bad strdup(%p) (%s)\n", file, line, s, ttoa(&tv));
    return NULL;
  }

  p = strdup(s);

  if (!p) {
    memlog("%s:%d: strdup(%p) returned 0 (%s)\n", file, line, s, ttoa(&tv));
    return NULL;
  }

  m = malloc(sizeof(memwatch_t));

  m->file = file;
  m->line = line;
  m->tv.tv_sec = tv.tv_sec;
  m->tv.tv_usec = tv.tv_usec;
  m->p = p;
  m->size = strlen(s)+1;
  m->tag = 0;

  list_prepend(memlist, m);
  
  if (nvar_default("memwatchverbose",0)) {
    memlog("%s:%d: strdup(%p) returned %p (%s)\n", file, line, s, p, ttoa(&tv));
  }
  return p;
}

int memwatch_vasprintf(char **strp, const char *fmt, va_list ap, char *file, int line) {
  int r;
  memwatch_t *m;
  struct timeval tv;

  gettimeofday(&tv, NULL);

  if (*strp != NULL) {
    memlog("%s:%d: warning: passing non-null pointer to vasprintf() (%s)\n", file, line, ttoa(&tv));
  }

  r = vasprintf(strp, fmt, ap);

  if (r == -1) {
    memlog("%s:%d: vasprintf() returned -1 (%s)\n", file, line, ttoa(&tv));
    return -1;
  }

  m = malloc(sizeof(memwatch_t));

  m->file = file;
  m->line = line;
  m->tv.tv_sec = tv.tv_sec;
  m->tv.tv_usec = tv.tv_usec;
  m->p = *strp;
  m->size = strlen(*strp)+1;
  m->tag = 0;

  list_prepend(memlist, m);
  
  if (nvar_default("memwatchverbose",0)) {
    memlog("%s:%d: vasprintf() allocated %p (%s)\n", file, line, *strp, ttoa(&tv));
  }
  return r;
}

/* clear all tags */
void memwatch_cleartags() {
  memwatch_t *m;

  list_forall(m, memlist) {
    m->tag = 0;
  }
}

/* tag a cell */
void memwatch_tag(void *p, int tag) {
  memwatch_t *m;
  struct timeval tv;

  if (!p) {
    return;
  }

  list_find(m, memlist, m->p == p);
  
  if (m==NULL) {
    gettimeofday(&tv, NULL);
    memlog("bad tag: %p %d (%s)\n", p, tag, ttoa(&tv));
    return;
  }
  
  m->tag = tag;
}

/* log the current memory usage */
void memwatch_dump() {
  memwatch_t *m;
  struct timeval tv;
  long age, uage;
  size_t size;
  int cells;
  int maxtag = 100;
  size_t size_tag[maxtag];
  int cells_tag[maxtag];
  int i;

  gettimeofday(&tv, NULL);

  memlog("----------------------------------------------------------------------\n");
  memlog("Memory footprint at %s\n", ttoa(&tv));

  cells = 0;
  size = 0;
  for (i=0; i<maxtag; i++) {
    size_tag[i] = 0;
    cells_tag[i] = 0;
  }

  list_forall(m, memlist) {
    size += m->size;
    cells += 1;
    size_tag[m->tag] += m->size;
    cells_tag[m->tag] += 1;

    age = tv.tv_sec - m->tv.tv_sec;
    uage = tv.tv_usec - m->tv.tv_usec;
    if (uage<0) {
      age -= 1;
      uage += 1000000;
    }
    memlog("%p size=[%4d] age=%ld.%06lds tag=%d created=%s:%d\n", m->p, m->size, age, uage, m->tag, m->file, m->line);
  }
  for (i=0; i<maxtag; i++) {
    if (cells_tag[i]) {
      memlog("Tag %d: %d cells, %d bytes\n", i, cells_tag[i], size_tag[i]);
    }
  }
  memlog("Total: %d cells, %d bytes\n", cells, size);
  memlog("----------------------------------------------------------------------\n");
}    
