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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <errno.h>

#if !defined(HAVE_CONFIG_H) && !defined(HAVE_MALLOC_H) && defined(SX)
#define HAVE_MALLOC_H
#endif

#if defined(HAVE_MALLOC_H)
#include <malloc.h>
#endif

#include "dmemory.h"

enum
{
  MALLOC_FUNC = 0,
  CALLOC_FUNC,
  REALLOC_FUNC,
  FREE_FUNC
};
static const char *const memfunc[] = { "Malloc", "Calloc", "Realloc", "Free" };

#undef MEM_UNDEFID
#define MEM_UNDEFID -1

#define MEM_MAXNAME 32 /* Min = 8, for  "unknown" ! */

static int dmemory_ExitOnError = 1;

typedef struct
{
  void *ptr;
  size_t size;
  size_t nobj;
  int item;
  int mtype;
  int line;
  char filename[MEM_MAXNAME];
  char functionname[MEM_MAXNAME];
} MemTable_t;

static MemTable_t *memTable;
static size_t memTableSize = 0;
static long memAccess = 0;

static size_t MemObjs = 0;
static size_t MaxMemObjs = 0;
static size_t MemUsed = 0;
static size_t MaxMemUsed = 0;

static int MEM_Debug = 0; /* If set to 1, debugging */
static int MEM_Info = 0;  /* If set to 1, print mem table at exit */

static const char *
get_filename(const char *file)
{
  const char *fnptr = strrchr(file, '/');
  if (fnptr)
    fnptr++;
  else
    fnptr = (char *) file;

  return fnptr;
}

void
memDebug(int debug)
{
  MEM_Debug = debug;
}

// If we're not using GNU C, elide __attribute__
#if !defined __GNUC__ && !defined __attribute__
#define __attribute__(x) /*NOTHING*/
#endif

static void memInternalProblem(const char *caller, const char *fmt, ...) __attribute__((noreturn));

static void memError(const char *caller, const char *file, int line, size_t size) __attribute__((noreturn));

static void
memInternalProblem(const char *caller, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);

  printf("\n");
  fprintf(stderr, "Internal problem (%s) : ", caller);
  vfprintf(stderr, fmt, args);
  fprintf(stderr, "\n");

  va_end(args);

  exit(EXIT_FAILURE);
}

static void
memError(const char *caller, const char *file, int line, size_t size)
{
  fputs("\n", stdout);
  fprintf(stderr, "Error (%s) : Allocation of %zu bytes failed. [ line %d file %s ]\n", caller, size, line, get_filename(file));

  if (errno) perror("System error message ");

  exit(EXIT_FAILURE);
}

static void
memListPrintEntry(int mtype, int item, size_t size, void *ptr, const char *caller, const char *file, int line)
{
  fprintf(stderr, "[%-7s ", memfunc[mtype]);

  fprintf(stderr, "memory item %3d ", item);
  fprintf(stderr, "(%6zu byte) ", size);
  fprintf(stderr, "at %p", ptr);
  if (file != NULL)
    {
      fprintf(stderr, " line %4d", line);
      fprintf(stderr, " file %s", get_filename(file));
    }
  if (caller != NULL) fprintf(stderr, " (%s)", caller);
  fprintf(stderr, "]\n");
}

static void
memListPrintTable(void)
{
  if (MemObjs) fprintf(stderr, "\nMemory table:\n");

  for (size_t memID = 0; memID < memTableSize; memID++)
    {
      if (memTable[memID].item != MEM_UNDEFID)
        memListPrintEntry(memTable[memID].mtype, memTable[memID].item, memTable[memID].size * memTable[memID].nobj,
                          memTable[memID].ptr, memTable[memID].functionname, memTable[memID].filename, memTable[memID].line);
    }

  if (MemObjs)
    {
      fprintf(stderr, "  Memory access             : %6u\n", (unsigned) memAccess);
      fprintf(stderr, "  Maximum objects           : %6zu\n", memTableSize);
      fprintf(stderr, "  Objects used              : %6u\n", (unsigned) MaxMemObjs);
      fprintf(stderr, "  Objects in use            : %6u\n", (unsigned) MemObjs);
      fprintf(stderr, "  Memory allocated          : ");
      if (MemUsed > 1024 * 1024 * 1024)
        fprintf(stderr, " %5d GB\n", (int) (MemUsed / (1024 * 1024 * 1024)));
      else if (MemUsed > 1024 * 1024)
        fprintf(stderr, " %5d MB\n", (int) (MemUsed / (1024 * 1024)));
      else if (MemUsed > 1024)
        fprintf(stderr, " %5d KB\n", (int) (MemUsed / (1024)));
      else
        fprintf(stderr, " %5d Byte\n", (int) MemUsed);
    }

  if (MaxMemUsed)
    {
      fprintf(stderr, "  Maximum memory allocated  : ");
      if (MaxMemUsed > 1024 * 1024 * 1024)
        fprintf(stderr, " %5d GB\n", (int) (MaxMemUsed / (1024 * 1024 * 1024)));
      else if (MaxMemUsed > 1024 * 1024)
        fprintf(stderr, " %5d MB\n", (int) (MaxMemUsed / (1024 * 1024)));
      else if (MaxMemUsed > 1024)
        fprintf(stderr, " %5d KB\n", (int) (MaxMemUsed / (1024)));
      else
        fprintf(stderr, " %5d Byte\n", (int) MaxMemUsed);
    }
}

static void
memGetDebugLevel(void)
{
  const char *envstr = getenv("MEMORY_INFO");
  if (envstr && isdigit((int) envstr[0])) MEM_Info = atoi(envstr);

  envstr = getenv("MEMORY_DEBUG");
  if (envstr && isdigit((int) envstr[0])) MEM_Debug = atoi(envstr);

  if (MEM_Debug && !MEM_Info) MEM_Info = 1;

  if (MEM_Info) atexit(memListPrintTable);
}

static void
memInit(void)
{
  static int initDebugLevel = 0;

  if (!initDebugLevel)
    {
      memGetDebugLevel();
      initDebugLevel = 1;
    }
}

static int
memListDeleteEntry(void *ptr, size_t *size)
{
  int item = MEM_UNDEFID;
  size_t memID = 0;

  for (memID = 0; memID < memTableSize; memID++)
    {
      if (memTable[memID].item == MEM_UNDEFID) continue;
      if (memTable[memID].ptr == ptr) break;
    }

  if (memID != memTableSize)
    {
      MemObjs--;
      MemUsed -= memTable[memID].size * memTable[memID].nobj;
      *size = memTable[memID].size * memTable[memID].nobj;
      item = memTable[memID].item;
      memTable[memID].item = MEM_UNDEFID;
    }

  return item;
}

static void
memTableInitEntry(size_t memID)
{
  if (memID >= memTableSize) memInternalProblem(__func__, "memID %d undefined!", memID);

  memTable[memID].ptr = NULL;
  memTable[memID].item = MEM_UNDEFID;
  memTable[memID].size = 0;
  memTable[memID].nobj = 0;
  memTable[memID].mtype = MEM_UNDEFID;
  memTable[memID].line = MEM_UNDEFID;
}

static void
set_filename(const char *file, char *memEntyFilename)
{
  if (file)
    {
      const char *filename = get_filename(file);
      size_t len = strlen(filename);
      if (len > MEM_MAXNAME - 1) len = MEM_MAXNAME - 1;

      (void) memcpy(memEntyFilename, filename, len);
      memEntyFilename[len] = '\0';
    }
  else
    {
      (void) strcpy(memEntyFilename, "unknown");
    }
}

static void
set_functionname(const char *functionname, char *memEntyFunctionname)
{
  if (functionname)
    {
      size_t len = strlen(functionname);
      if (len > MEM_MAXNAME - 1) len = MEM_MAXNAME - 1;

      (void) memcpy(memEntyFunctionname, functionname, len);
      memEntyFunctionname[len] = '\0';
    }
  else
    {
      (void) strcpy(memEntyFunctionname, "unknown");
    }
}

static int
memListNewEntry(int mtype, void *ptr, size_t size, size_t nobj, const char *functionname, const char *file, int line)
{
  static int item = 0;
  size_t memID = 0;

  // Look for a free slot in memTable (Create the table the first time through).
  if (memTableSize == 0)
    {
      memTableSize = 8;
      size_t memSize = memTableSize * sizeof(MemTable_t);
      memTable = (MemTable_t *) malloc(memSize);
      if (memTable == NULL) memError(__func__, __FILE__, __LINE__, memSize);

      for (size_t i = 0; i < memTableSize; i++) memTableInitEntry(i);
    }
  else
    {
      while (memID < memTableSize)
        {
          if (memTable[memID].item == MEM_UNDEFID) break;
          memID++;
        }
    }

  // If the table overflows, double its size.
  if (memID == memTableSize)
    {
      memTableSize = 2 * memTableSize;
      size_t memSize = memTableSize * sizeof(MemTable_t);
      memTable = (MemTable_t *) realloc(memTable, memSize);
      if (memTable == NULL) memError(__func__, __FILE__, __LINE__, memSize);

      for (size_t i = memID; i < memTableSize; i++) memTableInitEntry(i);
    }

  memTable[memID].item = item;
  memTable[memID].ptr = ptr;
  memTable[memID].size = size;
  memTable[memID].nobj = nobj;
  memTable[memID].mtype = mtype;
  memTable[memID].line = line;

  set_filename(file, memTable[memID].filename);
  set_functionname(functionname, memTable[memID].functionname);

  MaxMemObjs++;
  MemObjs++;
  MemUsed += size * nobj;
  if (MemUsed > MaxMemUsed) MaxMemUsed = MemUsed;

  return item++;
}

static int
memListChangeEntry(void *ptrold, void *ptr, size_t size, const char *functionname, const char *file, int line)
{
  int item = MEM_UNDEFID;
  size_t memID = 0;

  while (memID < memTableSize)
    {
      if (memTable[memID].item != MEM_UNDEFID && memTable[memID].ptr == ptrold) break;
      memID++;
    }

  if (memID == memTableSize)
    {
      if (ptrold != NULL) memInternalProblem(__func__, "Item at %p not found.", ptrold);
    }
  else
    {
      item = memTable[memID].item;

      const size_t sizeold = memTable[memID].size * memTable[memID].nobj;

      memTable[memID].ptr = ptr;
      memTable[memID].size = size;
      memTable[memID].nobj = 1;
      memTable[memID].mtype = REALLOC_FUNC;
      memTable[memID].line = line;

      set_filename(file, memTable[memID].filename);
      set_functionname(functionname, memTable[memID].functionname);

      MemUsed -= sizeold;
      MemUsed += size;
      if (MemUsed > MaxMemUsed) MaxMemUsed = MemUsed;
    }

  return item;
}

void *
memCalloc(size_t nobjs, size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (nobjs * size > 0)
    {
      ptr = calloc(nobjs, size);

      if (MEM_Info)
        {
          memAccess++;

          int item = MEM_UNDEFID;
          if (ptr) item = memListNewEntry(CALLOC_FUNC, ptr, size, nobjs, functionname, file, line);

          if (MEM_Debug) memListPrintEntry(CALLOC_FUNC, item, size * nobjs, ptr, functionname, file, line);
        }

      if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size * nobjs);
    }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, file);

  return ptr;
}

void *
memMalloc(size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (size > 0)
    {
      ptr = malloc(size);

      if (MEM_Info)
        {
          memAccess++;

          int item = MEM_UNDEFID;
          if (ptr) item = memListNewEntry(MALLOC_FUNC, ptr, size, 1, functionname, file, line);

          if (MEM_Debug) memListPrintEntry(MALLOC_FUNC, item, size, ptr, functionname, file, line);
        }

      if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size);
    }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, file);

  return ptr;
}

void *
memRealloc(void *ptrold, size_t size, const char *file, const char *functionname, int line)
{
  void *ptr = NULL;

  memInit();

  if (size > 0)
    {
      ptr = realloc(ptrold, size);

      if (MEM_Info)
        {
          memAccess++;

          int item = MEM_UNDEFID;
          if (ptr)
            {
              item = memListChangeEntry(ptrold, ptr, size, functionname, file, line);
              if (item == MEM_UNDEFID) item = memListNewEntry(REALLOC_FUNC, ptr, size, 1, functionname, file, line);
            }

          if (MEM_Debug) memListPrintEntry(REALLOC_FUNC, item, size, ptr, functionname, file, line);
        }

      if (ptr == NULL && dmemory_ExitOnError) memError(functionname, file, line, size);
    }
  else
    fprintf(stderr, "Warning (%s) : Allocation of 0 bytes! [ line %d file %s ]\n", functionname, line, get_filename(file));

  return ptr;
}

void
memFree(void *ptr, const char *file, const char *functionname, int line)
{
  memInit();

  if (MEM_Info)
    {
      size_t size = 0;
      const int item = memListDeleteEntry(ptr, &size);
      if (item >= 0)
        {
          if (MEM_Debug) memListPrintEntry(FREE_FUNC, item, size, ptr, functionname, file, line);
        }
      else
        {
          if (ptr && MEM_Debug)
            fprintf(stderr, "%s info: memory entry at %p not found. [line %4d file %s (%s)]\n", __func__, ptr, line,
                    get_filename(file), functionname);
        }
    }

  free(ptr);
}

size_t
memTotal(void)
{
  size_t memtotal = 0;
#ifdef HAVE_MALLINFO
  struct mallinfo meminfo = mallinfo();
  if (MEM_Debug)
    {
      fprintf(stderr, "arena      %8zu (non-mmapped space allocated from system)\n", (size_t) meminfo.arena);
      fprintf(stderr, "ordblks    %8zu (number of free chunks)\n", (size_t) meminfo.ordblks);
      fprintf(stderr, "smblks     %8zu (number of fastbin blocks)\n", (size_t) meminfo.smblks);
      fprintf(stderr, "hblks      %8zu (number of mmapped regions)\n", (size_t) meminfo.hblks);
      fprintf(stderr, "hblkhd     %8zu (space in mmapped regions)\n", (size_t) meminfo.hblkhd);
      fprintf(stderr, "usmblks    %8zu (maximum total allocated space)\n", (size_t) meminfo.usmblks);
      fprintf(stderr, "fsmblks    %8zu (maximum total allocated space)\n", (size_t) meminfo.fsmblks);
      fprintf(stderr, "uordblks   %8zu (total allocated space)\n", (size_t) meminfo.uordblks);
      fprintf(stderr, "fordblks   %8zu (total free space)\n", (size_t) meminfo.fordblks);
      fprintf(stderr, "Memory in use:   %8zu bytes\n", (size_t) meminfo.usmblks + (size_t) meminfo.uordblks);
      fprintf(stderr, "Total heap size: %8zu bytes\n", (size_t) meminfo.arena);

      // malloc_stats();
    }
  memtotal = (size_t) meminfo.arena;
#endif

  return memtotal;
}

void
memExitOnError(void)
{
  dmemory_ExitOnError = 1;
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
