#if !defined(PS_MEMORY_H)
#define PS_MEMORY_H
/** @file  psMemory.h
 *
 *  @brief Contains the definitions for the memory management system
 *
 *  This is the generic memory management system put inbetween the user's high level code and the OS-level
 *  memory allocation routines.  This system adds such features as callback routines for memory error events,
 *  tracing capabilities, and reference counting.
 *
 *  @author Robert DeSonia, MHPCC
 *  @author Robert Lupton, Princeton University
 *
 *  @ingroup MemoryManagement
 *
 *  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
 *  @date $Date: 2004-07-15 23:52:34 $
 *
 *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
 */

#include <stdio.h>                      // needed for FILE
#include <stdbool.h>
#include <pthread.h>                    // we need a mutex to make this stuff thread safe.

/** @addtogroup MemoryManagement
 *  @{
 */

/**
 *  @addtogroup memCallback Memory Callbacks
 *
 *  Routines dealing with the creating and setting of memory management callback functions.
 */

/**
 *  @addtogroup memTracing Memory Tracing
 *
 *  Routines dealing with memory tracing and corruption checking.
 */

/**
 *  @addtogroup memRefCount Reference Count
 *
 *  Routines dealing with the reference counting of allocated buffers.
 */

/// typedef for memory identification numbers.  Guaranteed to be some variety of integer.
typedef unsigned long psMemoryId;

/// typedef for a memory block's reference count. Guaranteed to be some variety of integer.
typedef unsigned long psReferenceCount;

/// typedef for deallocator.
typedef void (*psFreeFcn)(void* ptr);

/** Book-keeping data for storage allocator.
 *  N.b. sizeof(psMemBlock) must be chosen such that if ptr is a pointer
 *  returned by malloc, then ((char *)ptr + sizeof(psMemBlock)) is properly
 *  aligned for all storage types.
 */
typedef struct psMemBlock
{
    const void* startblock;            ///< initialised to p_psMEMMAGIC
    struct psMemBlock* previousBlock;  ///< previous block in allocation list
    struct psMemBlock* nextBlock;      ///< next block allocation list
    psFreeFcn freeFcn;                 ///< deallocator.  If NULL, use generic deallocation.
    size_t  userMemorySize;            ///< the size of the user-portion of the memory block
    const psMemoryId id;               ///< a unique ID for this allocation
    const char* file;                  ///< set from __FILE__ in e.g. p_psAlloc
    const int lineno;                  ///< set from __LINE__ in e.g. p_psAlloc
    pthread_mutex_t   refCounterMutex; ///< mutex to ensure exclusive access to reference counter
    psReferenceCount refCounter;       ///< how many times pointer is referenced
    const void* endblock;              ///< initialised to p_psMEMMAGIC
}
psMemBlock;

/** prototype of a basic callback used by memory functions
 *
 *  @see psMemAllocateCallbackSet
 *  @ingroup memCallback
 */
typedef psMemoryId (*psMemAllocateCallback)(
    const psMemBlock *ptr           ///< the psMemBlock just allocated
);

/** prototype of memory free callback used by memory functions
 *
 *  @see psMemFreeCallbackSet
 *  @ingroup memCallback
 */
typedef psMemoryId (*psMemFreeCallback)(
    const psMemBlock *ptr           ///< the psMemBlock being freed
);

/** prototype of a callback used in error conditions
 *
 *  This callback should not try to call psAlloc or psFree.
 *
 *  @see psMemProblemCallbackSet
 *  @ingroup memCallback
 */
typedef void (*psMemProblemCallback)(
    const psMemBlock *ptr,          ///< the pointer to the problematic memory block.
    const char *file,               ///< the file in which the problem originated
    int lineno                      ///< the line number in which the problem originated
);

/** prototype of a callback function used when memory runs out
 *
 *  @return void* pointer to requested buffer of the size size_t, or NULL if memory could not
 *          be found.
 *
 *  @see psMemExhaustedCallbackSet
 *  @ingroup memCallback
 */
typedef void *(*psMemExhaustedCallback)(
    size_t size                     //< the size of buffer required
);

/** Memory allocation.  This operates much like malloc(), but is guaranteed to return a non-NULL value.
 *
 *  @return void* pointer to the allocated buffer. This will not be NULL.
 *  @see psFree 
 */
#ifdef DOXYGEN
void* psAlloc(
    size_t size                     ///< Size required
);
#else
void* p_psAlloc(
    size_t size,                    ///< Size required
    const char *file,               ///< File of call
    int lineno                      ///< Line number of call
);

void p_psMemSetDeallocator(void* ptr, psFreeFcn freeFcn);
psFreeFcn p_psMemGetDeallocator(void* ptr);

/// Memory allocation. psAlloc sends file and line number to p_psAlloc.
#define psAlloc(size) p_psAlloc(size, __FILE__, __LINE__)
#endif


/** Memory re-allocation.  This operates much like realloc(), but is guaranteed to return a non-NULL value.
 *
 *  @return void* pointer to resized buffer. This will not be NULL.
 *  @see psAlloc, psFree
 */
#ifdef DOXYGEN
void* psRealloc(void *ptr               ///< Pointer to re-allocate
                size_t size,            ///< Size required
               );
#else
void* p_psRealloc(void *ptr,            ///< Pointer to re-allocate
                  size_t size,          ///< Size required
                  const char *file,     ///< File of call
                  int lineno            ///< Line number of call
                 );

/// Memory re-allocation.  psRealloc sends file and line number to p_psRealloc.
#define psRealloc(ptr, size) p_psRealloc(ptr, size, __FILE__, __LINE__)

#endif


/** Free memory.  This operates much like free().
 *
 *  @see psAlloc, psRealloc
 */
#ifdef DOXYGEN
void psFree(void *ptr,                  ///< Pointer to free, if NULL, function returns immediately.
           );
#else
void p_psFree(void *ptr,                ///< Pointer to free
              const char *file,         ///< File of call
              int lineno                ///< Line number of call
             );

/// Free memory.  psFree sends file and line number to p_psFree.
#define psFree(size) p_psFree(size, __FILE__, __LINE__)

#endif

/** Check for memory leaks.  This scans for allocated memory buffers not freed with an ID not less than id0.
 *  This is used to check for memory leaks by:
 *      -# before a block of code to be checked, store the current ID count via psGetMemId
 *      -# after the block of code to be checked, call this function using the ID stored above.  If all
 *         memory in the block that was allocated has been freed, this call should output nothing and
 *         return 0.
 *
 *  If memory leaks are found, the Memory Problem callback will be called as well.
 *
 *  return int  number of memory blocks found as 'leaks', i.e., the number of currently allocated memory
 *              blocks above id0 that have not been freed.
 *  @see psAlloc, psFree, psgetMemId, psMemProblemCallbackSet
 *  @ingroup memTracing
 */
int psMemCheckLeaks(
    psMemoryId id0,              ///< don't list blocks with id < id0
    psMemBlock ***arr,              ///< pointer to array of pointers to leaked blocks, or NULL
    FILE *fd                        ///< print list of leaks to fd (or NULL)
);

/** Check for memory corruption.  Scans all currently allocated memory buffers and checks for corruptions,
 *  i.e., invalid markers that signify a buffer under/overflow.
 *
 *  @ingroup memTracing
 */
int psMemCheckCorruption(
    bool abort_on_error              ///< Abort on detecting corruption?
);

/** Return reference counter
 *
 *  @ingroup memRefCount
 */
psReferenceCount psMemGetRefCounter(
    void *vptr                      ///< Pointer to get refCounter for
);

/** Increment reference counter and return the pointer
 *
 *  @ingroup memRefCount
 */
#ifdef DOXYGEN
void* psMemIncrRefCounter(
    void *vptr                         ///< Pointer to increment refCounter, and return
);
#else
void* p_psMemIncrRefCounter(
    void *vptr,                        ///< Pointer to increment refCounter, and return
    const char *file,                  ///< File of call
    int lineno                         ///< Line number of call
);
#define psMemIncrRefCounter(vptr) p_psMemIncrRefCounter(vptr, __FILE__, __LINE__)
#endif

/** Decrement reference counter and return the pointer
 *
 *  @ingroup memRefCount
 */
#ifdef DOXYGEN
void* psMemDecrRefCounter(
    void *vptr                         ///< Pointer to decrement refCounter, and return
);
#else
void* p_psMemDecrRefCounter(
    void *vptr,                        ///< Pointer to decrement refCounter, and return
    const char *file,                  ///< File of call
    int lineno                         ///< Line number of call
);
#define psMemDecrRefCounter(vptr) p_psMemDecrRefCounter(vptr, __FILE__, __LINE__)
#endif

/** Set callback for problems
 *  @ingroup memCallback
 */
psMemProblemCallback psMemProblemCallbackSet(
    psMemProblemCallback func       ///< Function to run
);

/** Set callback for out-of-memory
 *
 *  @ingroup memCallback
 */
psMemExhaustedCallback psMemExhaustedCallbackSet(
    psMemExhaustedCallback func     ///< Function to run
);

/** Set call back for when a particular memory block is allocated
 *
 *  @ingroup memCallback
 */
psMemAllocateCallback psMemAllocateCallbackSet(
    psMemAllocateCallback func          ///< Function to run
);

/** Set call back for when a particular memory block is freed
 *
 *  @ingroup memCallback
 */
psMemFreeCallback psMemFreeCallbackSet(
    psMemFreeCallback func              ///< Function to run
);

/** get next memory ID
 *
 *  @ingroup memCallback
 */
psMemoryId psMemGetId(void);

/** set p_psMemAllocateID to id
 *
 *  @ingroup memCallback
 */
psMemoryId psMemAllocateCallbackSetID(
    psMemoryId id                       ///< ID to set
);

/** set p_psMemFreeID to id
 *
 *  @ingroup memCallback
 */
psMemoryId psMemFreeCallbackSetID(
    psMemoryId id                       ///< ID to set
);

//@} End of Memory Management Functions

#ifndef DOXYGEN

/*
 * Ensure that any program using malloc/realloc/free will fail to compile
 */
#ifndef PS_ALLOW_MALLOC
#ifdef __GNUC__
#pragma GCC poison malloc realloc calloc free
#else
#define malloc(S)       _Pragma("error Use of malloc is not allowed.  Use psAlloc instead.")
#define realloc(P,S)    _Pragma("error Use of realloc is not allowed.  Use psRealloc instead.")
#define calloc(S)       _Pragma("error Use of calloc is not allowed.  Use psAlloc instead.")
#define free(P)         _Pragma("error Use of free is not allowed.  Use psFree instead.")
#endif
#endif

#endif // doxygen skip

#endif // end of header file
