# include <stdio.h>
# include <stdlib.h>
# include <stdarg.h>

/* need an internal version that does not use ohana_memory functions */
# define FALSE 0 
# define TRUE 1
# define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
# define OHANA_ALLOCATE(X,T,S)  \
  X = (T *) malloc ((unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
  if (X == NULL) { \
    fprintf(stderr,"failed malloc at %d in %s\n", __LINE__, __FILE__);\
    abort(); } 
# define OHANA_REALLOCATE(X,T,S) \
  X = (T *) realloc(X,(unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
  if (X == NULL) { \
    fprintf(stderr,"failed realloc at %d in %s\n", __LINE__, __FILE__);\
    abort(); }
# define OHANA_CHECK_REALLOCATE(X,T,S,N,D) \
  if ((N) >= (S)) { \
    S += D; \
    X = (T *) realloc(X,(unsigned)(MAX(((S)*((int)sizeof(T))),1))); \
    if (X == NULL) { \
      fprintf(stderr,"failed realloc increment at %d in %s\n", __LINE__, __FILE__);\
      abort(); } }
# define OHANA_FREE(X) free(X)

# define STATE_ALLOC    0
# define STATE_REALLOC  1
# define STATE_FREE     2
# define STATE_EXTERNAL 3

typedef struct {
  char *file;
  int   line;
  void *ptr;   // location of externally visible entry
  int   size;
  int   state;
} Memlist;

// XXX consider fixing the memory tracking model (list?)
// static long *memsort;
// static long *memhash;
static Memlist *memlist = NULL;
int Nmemlist = 0;
int NMEMLIST = 0;
int NMEMBYTE = 0;

void ohana_meminit () {
  Nmemlist = 0;
  NMEMLIST = 1000;
  OHANA_ALLOCATE (memlist, Memlist, NMEMLIST);
  // OHANA_ALLOCATE (memsort, long, NMEMLIST);
  // OHANA_ALLOCATE (memhash, long, NMEMLIST);
  NMEMBYTE = sizeof(size_t);
}

void ohana_memabort (char *format, ...) {
  va_list argp;  

  va_start (argp, format);
  vfprintf (stderr, format, argp);
  va_end (argp);
  abort();
}

void *ohana_malloc (char *file, int line, int Nelem, size_t esize) {

  void *ptr, *new;
  int size;
  size_t *marker;

  if (memlist == NULL) ohana_meminit ();

  Nelem = MAX (1, Nelem);
  size = Nelem * esize;

  new = malloc (size + 2*NMEMBYTE); /* 2 extra locations to save endposts */
  if (new == NULL) ohana_memabort ("failed to allocate memory (%s, %d)\n", file, line);
  ptr = new + NMEMBYTE;

  marker = (size_t *) new;
  *marker = 0xdeadbeef;
  marker = (size_t *)(new + size + NMEMBYTE);
  *marker = 0xdeadbeef;

  /* new memory, add to stack */
  memlist[Nmemlist].ptr  = ptr;
  memlist[Nmemlist].file = file;
  memlist[Nmemlist].line = line;
  memlist[Nmemlist].size = size;
  memlist[Nmemlist].state = STATE_ALLOC;

  // before each free, we sort this pair to speed up searching
  // memsort[Nmemlist] = ptr;
  // memhash[Nmemlist] = Nmemlist;

  Nmemlist ++;
  if (Nmemlist == NMEMLIST) {
    NMEMLIST += 1000;
    OHANA_REALLOCATE (memlist, Memlist, NMEMLIST);
  }
  return (ptr);
}

void *ohana_realloc (char *file, int line, void *in, int Nelem, size_t esize) {

  int i, size;
  void *ptr, *ref, *new;
  size_t *marker;

  if (memlist == NULL) ohana_memabort ("REALLOCATE before ALLOCATE");

  Nelem = MAX (1, Nelem);
  size = Nelem * esize;

  ref = in - NMEMBYTE;

  /* find old entry, update ptr, file, line */
  for (i = 0; i < Nmemlist; i++) {
    if (memlist[i].state == STATE_FREE) continue;
    if (memlist[i].ptr == in) {

      if (memlist[i].state == STATE_EXTERNAL) ohana_memabort ("ERROR: realloc of external memory");

      /* ask for new memory */
      new = realloc (ref, size + 2*NMEMBYTE); /* 2 extra slots to save endposts */
      if (new == NULL) ohana_memabort ("failed to reallocate memory (%s, %d)\n", file, line);
      ptr = new + NMEMBYTE;
      
      /* set the marker */
      marker = (size_t *) new;
      *marker = 0xdeadbeef;
      marker = (size_t *)(ptr + size);
      *marker = 0xdeadbeef;

      /* otherwise, update memory new location */
      memlist[i].ptr = ptr;
      memlist[i].size = size;
      memlist[i].state = STATE_REALLOC;

      /* if new memory in new location, update references */
      if (ptr != in) {
	memlist[i].file = file;
	memlist[i].line = line;
      }

      return (ptr);
    }
  }
  ohana_memabort ("allocated memory not found for realloc (%s, %d)\n", file, line);
  return (NULL);
}

// this is very slow.  should we speed this up by indexing on the ptr?
void ohana_free (char *file, int line, void *in) {

  int i;

  if (memlist == NULL) ohana_memabort ("FREE before ALLOCATE");

  /* find old entry, set state */
  for (i = 0; i < Nmemlist; i++) {
    if (memlist[i].state == STATE_FREE) continue;
    if (memlist[i].ptr == in) {
      memlist[i].state = STATE_FREE;
      return;
    }
  }

  /* find already freed examples */
  for (i = 0; i < Nmemlist; i++) {
    if (memlist[i].ptr == in) {
      fprintf (stderr, "used here: %4d %s %d\n", i, memlist[i].file, memlist[i].line);
    }
  }

  ohana_memabort ("allocated memory not found for free (%s, %d : %x)\n", file, line, in);
  return;
}

/* register externally allocated memory with ohana memory manager */
void ohana_memregister_func (char *file, int line, void *ptr) {

  if (memlist == NULL) ohana_meminit ();

  /* add to stack */
  memlist[Nmemlist].ptr  = ptr;
  memlist[Nmemlist].file = file;
  memlist[Nmemlist].line = line;
  memlist[Nmemlist].state = STATE_EXTERNAL;
  Nmemlist ++;
  if (Nmemlist == NMEMLIST) {
    NMEMLIST += 1000;
    OHANA_REALLOCATE (memlist, Memlist, NMEMLIST);
  }
  return;
}

void ohana_memcheck_func (int allmemory) {

  int i, j, next, prev, header;
  size_t *marker;
  char top, bottom;

  if (Nmemlist == 0) {
    fprintf (stderr, "no memory allocated\n");
    return;
  }

  header = FALSE;
  fprintf (stderr, "checking %d memory blocks\n", Nmemlist);
  for (i = 0; i < Nmemlist; i++) {
    if (memlist[i].state == STATE_EXTERNAL) continue;
    if ((allmemory == 0) && (memlist[i].state == STATE_FREE)) continue;

    top = bottom = 'Y';
    marker = (size_t *)(memlist[i].ptr - NMEMBYTE);
    if (*marker == 0xdeadbeef) bottom = 'N';
    marker = (size_t *)(memlist[i].ptr + memlist[i].size);
    if (*marker == 0xdeadbeef) top = 'N';
    
    if ((top == 'N') && (bottom == 'N')) continue;
    if (!header) {
      fprintf (stderr, "         Nmem start end file            line\n");
      header = TRUE;
    }
    fprintf (stderr, "corrupt:  %3d   %c    %c  %-15s %3d\n", i, bottom, top, memlist[i].file, memlist[i].line);
    prev = next = -1;
    for (j = 0; j < Nmemlist; j++) {
      if (memlist[j].ptr < memlist[i].ptr) {
	if (prev == -1) prev = j;
	if (memlist[j].ptr > memlist[prev].ptr) prev = j;
      }
      if (memlist[j].ptr > memlist[i].ptr) {
	if (next == -1) next = j;
	if (memlist[j].ptr < memlist[next].ptr) next = j;
      }
    }	  
    if (prev == -1) {
      fprintf (stderr, "no previous memory block\n");
    } else {
      fprintf (stderr, "prev:     %3d           %-15s %3d\n", prev, memlist[prev].file, memlist[prev].line);
    }
    if (next == -1) {
      fprintf (stderr, "no next memory block\n");
    } else {
      fprintf (stderr, "next:     %3d           %-15s %3d\n", next, memlist[next].file, memlist[next].line);
    }
  }
  return;
}

void ohana_memdump_func (int allmemory) {

  int i, Ns[3], N;
  char S[3];

  if (Nmemlist == 0) {
    fprintf (stderr, "no memory allocated\n");
    return;
  }

  S[0] = 'A';
  S[1] = 'R';
  S[2] = 'F';
  S[3] = 'X';
  Ns[0] = Ns[1] = Ns[2] = Ns[3] = 0;
  N = 0;

  for (i = 0; i < Nmemlist; i++) {
    if (memlist[i].state == STATE_ALLOC)    N = 0;
    if (memlist[i].state == STATE_REALLOC)  N = 1;
    if (memlist[i].state == STATE_FREE)     N = 2;
    if (memlist[i].state == STATE_EXTERNAL) N = 2;
    Ns[N] ++;
    if ((allmemory == 0) && (memlist[i].state == STATE_FREE)) continue;
    if (memlist[i].state == STATE_EXTERNAL) {
      fprintf (stderr, "%3d %4d %lx : %lx - extern (extern     extern) %c %s %d\n", 
	       Ns[N], i, (long)memlist[i].ptr, (long)memlist[i].ptr, 
	       S[N], memlist[i].file, memlist[i].line);
    } else {
      fprintf (stderr, "%3d %4d %lx : %lx - %lx (%lx %lx) %c %s %d\n", 
	       Ns[N], i, (long)memlist[i].ptr, (long)(memlist[i].ptr - NMEMBYTE), (long)(memlist[i].ptr + memlist[i].size + NMEMBYTE), 
	       *(long *)(memlist[i].ptr - NMEMBYTE), *(long *)(memlist[i].ptr + memlist[i].size), 
	       S[N], memlist[i].file, memlist[i].line);
    }
  }
  fprintf (stderr, "Nused: %d (Nalloc: %d, Nrealloc: %d), Nfree: %d\n", 
	   Ns[0]+Ns[1], Ns[0], Ns[1], Ns[2]);
  return;
}
