/** @file  psMemory.c
*
*  @brief Contains the definitions for the memory management system
*
*  psMemory.h has additional information and documentation of the routines found in this file.
*
*  @author Robert DeSonia, MHPCC
*  @author Robert Lupton, Princeton University
*
*  @version $Revision: 1.87 $ $Name: not supported by cvs2svn $
*  @date $Date: 2006-11-29 17:58:21 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

#define PS_ALLOW_MALLOC                    // we're allowed to call malloc()

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

#include "psMemory.h"
#include "psError.h"
#include "psAssert.h"
#include "psAbort.h"
#include "psLogMsg.h"
#include "psBitSet.h"
#include "psFits.h"
#include "psPixels.h"
#include "psSphereOps.h"
#include "psMinimizeLMM.h"
#include "psImageConvolve.h"
#include "psTime.h"
#include "psLine.h"
#include "psRegion.h"

#define P_PS_LARGE_BLOCK_SIZE 65536        // size where under, we try to recycle

static psS32 checkMemBlock(const psMemBlock* m, const char *funcName);
static psMemBlock* lastMemBlockAllocated = NULL;
static pthread_mutex_t memBlockListMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t memIdMutex = PTHREAD_MUTEX_INITIALIZER;

//private boolean for enabling/disabling thread safety.  Default = enabled.
static bool safeThreads = true;

// private boolean for deciding if allocated memory is persistent
static bool memory_is_persistent = false;

#ifdef PS_MEM_USE_RECYCLE               // Only use recycling if this is set
#define N_RECYCLE_BINS 14               // number of recycle bins
#define MAX_RECYCLE 100                 // Maximum number permitted in a recycle bin
static pthread_mutex_t recycleMemBlockListMutex = PTHREAD_MUTEX_INITIALIZER; // Mutex for recycle bins
static const psS32 recycleBinSize[N_RECYCLE_BINS] = // Size of each bin
    {
        8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, P_PS_LARGE_BLOCK_SIZE
    };
// N.B. recycleBinSize should be terminated by P_PS_LARGE_BLOCK_SIZE (simplifies search loops)
static psS32 recycleBinNums[N_RECYCLE_BINS] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // Number in each bin
static psMemBlock* recycleMemBlockList[N_RECYCLE_BINS] = // Contents of the bins
    { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
#endif // #ifdef PS_MEM_USE_RECYCLE

#ifdef PS_MEM_DEBUG
static psMemBlock* deadBlockList;       // a place to put dead memBlocks in debug mode.
#endif // #ifdef PS_MEM_DEBUG

/**
 * Unique ID for allocated blocks
 */
static psMemId memid = 0;

/**
 *  Default memExhausted callback.
 */
static psPtr memExhaustedCallbackDefault(size_t size)
{
    #if PS_MEM_USE_RECYCLE
    psPtr ptr = NULL;
    if (safeThreads) {
        pthread_mutex_lock(&recycleMemBlockListMutex);
    }
    // Attempt to free up everything I can find so I can alloc my ptr
    int bin = N_RECYCLE_BINS - 1;       // Recycle bin

    while (bin >= 0 && ptr == NULL) {
        while (recycleMemBlockList[bin] != NULL && ptr == NULL) {
            psMemBlock *old = recycleMemBlockList[bin];
            recycleMemBlockList[bin] = recycleMemBlockList[bin]->nextBlock;
            free(old);
            ptr = malloc(size);
        }
        bin--;
    }

    if (safeThreads) {
        pthread_mutex_unlock(&recycleMemBlockListMutex);
    }
    return ptr;
    #else  // #ifdef PS_MEM_USE_RECYCLE

    return NULL;
    #endif // #ifdef PS_MEM_USE_RECYCLE
}

/*
 * Default callback for both allocate and free. Note that the
 * value of p_psMemAllocID/p_psMemFreeID is incremented
 * by the return value (so returning 0 means that the callback
 * isn't resignalled)
 */
static psMemId memAllocCallbackDefault(const psMemBlock* ptr)
{
    static psMemId incr = 0; // "p_psMemAllocID += incr"

    return incr;
}

static psMemId memFreeCallbackDefault(const psMemBlock* ptr)
{
    static psMemId incr = 0; // "p_psMemFreeID += incr"

    return incr;
}

static void memProblemCallbackDefault(psMemBlock* ptr,
                                      const char *file,
                                      unsigned int lineno)
{
    if (ptr->refCounter < 1) {
        psError(PS_ERR_MEMORY_CORRUPTION, false,
                _("Block %lu, allocated at %s:%d, freed multiple times at %s:%d."),
                (unsigned long)ptr->id, ptr->file, ptr->lineno, file, lineno);
    }

    if (lineno > 0) {
        psAbort(__func__, "Detected a problem in the memory system at %s:%d", file, lineno);
    }
}

/*
 * Routines to check the consistency of the allocated and/or free memory arena
 *
 * N.b. If the block wasn't allocated by psAlloc, it will appear corrupted
 */
static psS32 checkMemBlock(const psMemBlock* m,
                           const char *funcName)
{
    // n.b. since this is called by psMemCheckCorruption while the memblock list is mutex locked,
    // we shouldn't call such things as p_psAlloc/p_psFree here.

    if (m == NULL) {
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                _("NULL memory block found."));
        return 1;
    }

    if (m->refCounter == 0) {
        // using an unreferenced block of memory, are you?
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                _("Memory block %lu was freed but still being used."),
                (unsigned long)m->id);
        return 1;
    }

    if (m->startblock != P_PS_MEMMAGIC || m->endblock != P_PS_MEMMAGIC) {
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                _("Memory block %lu is corrupted; buffer underflow detected."),
                (unsigned long)m->id);
        return 1;
    }
    if (*(psPtr *)((int8_t *) (m + 1) + m->userMemorySize) != P_PS_MEMMAGIC) {
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                _("Memory block %lu is corrupted; buffer overflow detected."),
                (unsigned long)m->id);
        return 1;
    }

    return 0;
}


/*
 * A callback function to check the state of the memory system; may be registered
 * with psMem{Alloc,Free}CallbackSet
 */
static psMemId memAllocCallbackCheckCorruption(const psMemBlock *ptr)
{
    static psMemId incr = 10; // "p_psMemAllocID += incr"
    bool abort_on_error = false;

    if (psMemCheckCorruption(abort_on_error) > 0) {
        fprintf(stderr,"Detected memory corruption\n"); // somewhere to set a breakpoint
    }

    return incr;
}

/*
 * The default callbacks
 */
static psMemAllocCallback memAllocCallback = memAllocCallbackDefault;
static psMemFreeCallback memFreeCallback = memFreeCallbackDefault;
static psMemProblemCallback memProblemCallback = memProblemCallbackDefault;
static psMemExhaustedCallback memExhaustedCallback = memExhaustedCallbackDefault;

psMemExhaustedCallback psMemExhaustedCallbackSet(psMemExhaustedCallback func)
{
    psMemExhaustedCallback old = memExhaustedCallback;

    if (func != NULL) {
        memExhaustedCallback = func;
    } else {
        memExhaustedCallback = memExhaustedCallbackDefault;
    }

    return old;
}

psMemProblemCallback psMemProblemCallbackSet(psMemProblemCallback func)
{
    psMemProblemCallback old = memProblemCallback;

    if (func != NULL) {
        memProblemCallback = func;
    } else {
        memProblemCallback = memProblemCallbackDefault;
    }

    return old;
}

/*
 * And now the I-want-to-be-informed callbacks
 *
 * Call the callbacks when these IDs are allocated/freed
 */
psMemId p_psMemAllocID = 0;       // notify user this block is allocated
psMemId p_psMemFreeID = 0;   // notify user this block is freed

psMemId psMemAllocCallbackSetID(psMemId id)
{
    psMemId old = p_psMemAllocID;

    /*
     * This is here purely to stop gcc complaining
     */
    assert (memAllocCallbackCheckCorruption != NULL);

    p_psMemAllocID = id;

    return old;
}

psMemId psMemFreeCallbackSetID(psMemId id)
{
    psMemId old = p_psMemFreeID;

    p_psMemFreeID = id;

    return old;
}

psMemAllocCallback psMemAllocCallbackSet(psMemAllocCallback func)
{
    psMemFreeCallback old = memAllocCallback;

    if (func != NULL) {
        memAllocCallback = func;
    } else {
        memAllocCallback = memAllocCallbackDefault;
    }

    return old;
}

psMemFreeCallback psMemFreeCallbackSet(psMemFreeCallback func)
{
    psMemFreeCallback old = memFreeCallback;

    if (func != NULL) {
        memFreeCallback = func;
    } else {
        memFreeCallback = memFreeCallbackDefault;
    }

    return old;
}

/*
 * Return memory ID counter for next block to be allocated
 */
psMemId psMemGetId(void)
{
    return psMemGetLastId() + 1;
}

psMemId psMemGetLastId(void)
{
    if (safeThreads) {
        pthread_mutex_lock(&memIdMutex);
    }

    psMemId id = memid;

    if (safeThreads) {
        pthread_mutex_unlock(&memIdMutex);
    }

    return id;
}

int psMemCheckCorruption(bool abort_on_error)
{
    psS32 nbad = 0;               // number of bad blocks
    psBool failure = false;

    // get exclusive access to the memBlock list to avoid it changing on us while we use it.
    //    pthread_mutex_lock(&memBlockListMutex);

    for (psMemBlock* iter = lastMemBlockAllocated; iter != NULL; iter = iter->nextBlock) {
        if (safeThreads) {
            pthread_mutex_unlock(&memBlockListMutex);
        }
        failure = checkMemBlock(iter, __func__);
        if (safeThreads) {
            pthread_mutex_lock(&memBlockListMutex);
        }
        if ( failure ) {
            nbad++;

            memProblemCallback(iter, __func__, __LINE__);

            if (abort_on_error) {
                // release the lock on the memblock list
                if (safeThreads) {
                    pthread_mutex_unlock(&memBlockListMutex);
                }
                psAbort(__func__, "Detected memory corruption");
                return nbad;
            }
        }
    }

    // release the lock on the memblock list
    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }
    return nbad;
}

/*
 * Set whether allocated memory is persistent
 */
bool p_psMemAllocatePersistent(bool is_persistent)
{
    const bool old = memory_is_persistent;
    memory_is_persistent = is_persistent;

    return old;
}

#ifdef PS_MEM_USE_RECYCLE
// Return the appropriate recycle bin number
static int getRecycleBin(size_t size)
{
    if (size >= P_PS_LARGE_BLOCK_SIZE) {
        return N_RECYCLE_BINS;
    }
    int binNum = 0;                     // Recycle bin number
    while (size > recycleBinSize[binNum]) {
        binNum++;
    }
    return binNum;
}

// Push a pointer onto the recycle bin
static void recyclePush(psMemBlock *mb, // Pointer to push
                        int bin         // Recycling bin
                       )
{
    assert(mb);
    assert(bin < N_RECYCLE_BINS);

    mb->previousBlock = NULL;

    if (safeThreads) {
        pthread_mutex_lock(&recycleMemBlockListMutex);
    }

    mb->nextBlock = recycleMemBlockList[bin];
    if (recycleMemBlockList[bin] != NULL) {
        recycleMemBlockList[bin]->previousBlock = mb;
    }
    recycleMemBlockList[bin] = mb;
    recycleBinNums[bin]++;

    if (safeThreads) {
        pthread_mutex_unlock(&recycleMemBlockListMutex);
    }

    return;
}

// Pop a pointer from the recycle bin
static psMemBlock *recyclePop(int bin   // Recycling bin
                             )
{
    if (bin >= N_RECYCLE_BINS) {
        return NULL;
    }

    if (safeThreads) {
        pthread_mutex_lock(&recycleMemBlockListMutex);
    }

    psMemBlock *mb = recycleMemBlockList[bin]; // Pointer popped from recycle bin

    if (mb) {
        // We pull it off the list
        recycleMemBlockList[bin] = mb->nextBlock;
        if (recycleMemBlockList[bin] != NULL) {
            recycleMemBlockList[bin]->previousBlock = NULL;
        }
        recycleBinNums[bin]--;
    }

    if (safeThreads) {
        pthread_mutex_unlock(&recycleMemBlockListMutex);
    }

    return mb;
}
#endif // #ifdef PS_MEM_USE_RECYCLE

/*
 * Actually allocate memory
 */
psPtr p_psAlloc(size_t size,
                const char *file,
                unsigned int lineno)
{

    psMemBlock *ptr = NULL;

    #ifdef PS_MEM_USE_RECYCLE
    // Are we in one of the recycle bins?
    int bin = getRecycleBin(size);
    if (bin < N_RECYCLE_BINS) {
        size = recycleBinSize[bin];     // round-up size to next sized bin.
        ptr = recyclePop(bin);          // grab out of the recycle bin
    }
    #endif // #ifdef PS_MEM_USE_RECYCLE

    if (ptr == NULL) {
        ptr = malloc(sizeof(psMemBlock) + size + sizeof(psPtr ));

        if (ptr == NULL) {
            ptr = memExhaustedCallback(size);
            if (ptr == NULL) {
                psAbort(__func__, "Failed to allocate %zd bytes at %s:%d", size, file, lineno);
            }
        }

        *(psPtr*)&ptr->startblock = P_PS_MEMMAGIC;
        *(psPtr*)&ptr->endblock = P_PS_MEMMAGIC;
        ptr->userMemorySize = size;
        if (safeThreads) {
            pthread_mutex_init(&ptr->refCounterMutex, NULL);
        }
    }
    // increment the memory id safely.
    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }
    *(psMemId* ) & ptr->id = ++memid;
    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }
    ptr->file = file;
    ptr->freeFunc = NULL;
    ptr->persistent = memory_is_persistent;
    *(psU32 *)&ptr->lineno = lineno;
    *(psPtr *)((int8_t *) (ptr + 1) + size) = P_PS_MEMMAGIC;
    ptr->previousBlock = NULL;

    ptr->refCounter = 1;                   // one user so far

    // need exclusive access of the memory block list now...
    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }

    // insert the new block to the front of the memBlock linked-list
    ptr->nextBlock = lastMemBlockAllocated;
    if (ptr->nextBlock != NULL) {
        ptr->nextBlock->previousBlock = ptr;
    }
    lastMemBlockAllocated = ptr;

    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }

    // Did the user ask to be informed about this allocation?
    if (ptr->id == p_psMemAllocID) {
        p_psMemAllocID += memAllocCallback(ptr);
    }
    // And return the user the memory that they allocated
    return ptr + 1;                        // user memory
}

psPtr p_psRealloc(psPtr vptr,
                  size_t size,
                  const char *file,
                  unsigned int lineno)
{
    if (vptr == NULL) {
        return p_psAlloc(size, file, lineno);
    }

    psMemBlock *ptr = ((psMemBlock*)vptr) - 1;

    if (checkMemBlock(ptr, __func__) != 0) {
        memProblemCallback(ptr, file, lineno);
        psAbort(file, "Realloc detected a memory corruption (id %lu @ %s:%d).",
                (unsigned long)ptr->id, ptr->file, ptr->lineno);
    }

    #ifdef PS_MEM_USE_RECYCLE
    // Ensure the size matches that of the recycle bin
    int bin = getRecycleBin(size);
    if (bin < N_RECYCLE_BINS) {
        size = recycleBinSize[bin];     // round-up size to next sized bin.
    }
    #endif // #ifdef PS_MEM_USE_RECYCLE

    if (size == ptr->userMemorySize) {
        // Nothing to do
        return vptr;
    }

    // Reallocate the memory

    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }

    bool isBlockLast = (ptr == lastMemBlockAllocated); // Is this the last block we allocated?
    ptr = (psMemBlock *)realloc(ptr, sizeof(psMemBlock) + size + sizeof(psPtr));
    if (ptr == NULL) {
        ptr = memExhaustedCallback(size);
        if (ptr == NULL) {
            psAbort(__func__, "Failed to reallocate %zd bytes at %s:%d", size, file, lineno);
        }
    }

    ptr->userMemorySize = size;
    *(psPtr *)((int8_t *) (ptr + 1) + size) = P_PS_MEMMAGIC;

    if (isBlockLast) {
        lastMemBlockAllocated = ptr;
    }

    // the block location may have changed, so fix the linked list addresses.
    if (ptr->nextBlock != NULL) {
        ptr->nextBlock->previousBlock = ptr;
    }
    if (ptr->previousBlock != NULL) {
        ptr->previousBlock->nextBlock = ptr;
    }

    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }

    // Did the user ask to be informed about this allocation?
    if (ptr->id == p_psMemAllocID) {
        p_psMemAllocID += memAllocCallback(ptr);
    }

    return ptr + 1;                    // usr memory
}

void p_psFree(psPtr vptr,
              const char *filename,
              unsigned int lineno)
{
    if (vptr == NULL) {
        return;
    }
    psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;
    if (ptr->refCounter < 1) {
        psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;

        psAbort(__func__,_("Block %lu, allocated at %s:%d, freed multiple times at %s:%d."),
                (unsigned long)ptr->id, ptr->file, ptr->lineno, filename, lineno);
    }

    if (checkMemBlock(ptr, __func__) != 0) {
        memProblemCallback(ptr, filename, lineno);
        psAbort(__func__,"Memory Corruption Detected.");
    }

    (void)p_psMemDecrRefCounter(vptr, filename, lineno);    // this handles the free, if required.
}

/*
 * Check for memory leaks.
 */
int psMemCheckLeaks(psMemId id0,
                    psMemBlock ***array,
                    FILE * fd,
                    bool persistence)
{
    psS32 nleak = 0;
    psS32 j = 0;
    psMemBlock* topBlock = lastMemBlockAllocated;

    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }

    for (psMemBlock* iter = topBlock; iter != NULL; iter = iter->nextBlock) {
        if ( (iter->refCounter > 0) &&
                ( (persistence) || (!persistence && !iter->persistent) ) &&
                (iter->id >= id0)) {

            nleak++;

            if (fd != NULL) {
                if (nleak == 1) {
                    fprintf(fd, "   %20s:line ID\n", "file");
                }

                fprintf(fd, "   %20s:%-4d %lu\n", iter->file, (int)iter->lineno, (unsigned long)iter->id);
            }
        }
    }

    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }

    if (nleak == 0 || array == NULL) {
        return nleak;
    }

    *array = p_psAlloc(nleak * sizeof(psMemBlock), __FILE__, __LINE__);
    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }

    for (psMemBlock* iter = topBlock; iter != NULL; iter = iter->nextBlock) {
        if ( (iter->refCounter > 0) &&
                ( (persistence) || (!persistence && !iter->persistent) ) &&
                (iter->id >= id0)) {

            (*array)[j++] = iter;
            if (j == nleak) {              // found them all
                break;
            }
        }
    }

    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }

    return nleak;
}

/*
 * Reference counting APIs
 */

// return refCounter
psReferenceCount psMemGetRefCounter(const psPtr ptr)
{
    psMemBlock* ptr2;
    psU32 refCount;

    if (ptr == NULL) {
        return 0;
    }

    ptr2 = ((psMemBlock* ) ptr) - 1;

    if (checkMemBlock(ptr2, __func__) != 0) {
        memProblemCallback(ptr2, __func__, __LINE__);
    }

    if (safeThreads) {
        pthread_mutex_lock(&ptr2->refCounterMutex);
    }
    refCount = ptr2->refCounter;
    if (safeThreads) {
        pthread_mutex_unlock(&ptr2->refCounterMutex);
    }

    return refCount;
}

// increment and return refCounter
psPtr p_psMemIncrRefCounter(const psPtr vptr,
                            const char *file,
                            psS32 lineno)
{
    psMemBlock* ptr;

    if (vptr == NULL) {
        return vptr;
    }

    ptr = ((psMemBlock* ) vptr) - 1;

    if (checkMemBlock(ptr, __func__)) {
        memProblemCallback(ptr, file, lineno);
    }

    if (safeThreads) {
        pthread_mutex_lock(&ptr->refCounterMutex);
    }
    ptr->refCounter++;
    if (safeThreads) {
        pthread_mutex_unlock(&ptr->refCounterMutex);
    }

    // Did the user ask to be informed about this allocation?
    if (ptr->id == p_psMemAllocID) {
        p_psMemAllocID += memAllocCallback(ptr);
    }

    return vptr;
}

psPtr p_psMemSetRefCounter(psPtr vptr,
                           psReferenceCount count,
                           const char *file,
                           psS32 lineno)
{
    psMemBlock* ptr;

    if (vptr == NULL) {
        return vptr;
    }

    if (count < 0) {
        count = 0;
    }

    ptr = ((psMemBlock* ) vptr) - 1;

    if (checkMemBlock(ptr, __func__)) {
        memProblemCallback(ptr, file, lineno);
    }

    if (safeThreads) {
        pthread_mutex_lock(&ptr->refCounterMutex);
    }
    ptr->refCounter = count;
    if (safeThreads) {
        pthread_mutex_unlock(&ptr->refCounterMutex);
    }

    if (count < 1) {
        vptr = p_psMemDecrRefCounter(vptr,file,lineno);
    }

    return vptr;
}

// decrement and return refCounter
psPtr p_psMemDecrRefCounter(psPtr vptr,
                            const char *file,
                            psS32 lineno)
{
    if (vptr == NULL) {
        return NULL;
    }

    psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;

    if (checkMemBlock(ptr, __func__) != 0) {
        memProblemCallback(ptr, file, lineno);
        return NULL;
    }

    // Did the user ask to be informed about this deallocation?
    if (ptr->id == p_psMemFreeID) {
        p_psMemFreeID += memFreeCallback(ptr);
    }

    if (safeThreads) {
        pthread_mutex_lock(&ptr->refCounterMutex);
    }

    if (ptr->refCounter > 1) {
        ptr->refCounter--;                 // multiple references, just decrement the count.
        if (safeThreads) {
            pthread_mutex_unlock(&ptr->refCounterMutex);
        }

    } else {
        if (safeThreads) {
            pthread_mutex_unlock(&ptr->refCounterMutex);
        }

        if (ptr->freeFunc != NULL) {
            ptr->freeFunc(vptr);
        }

        if (safeThreads) {
            pthread_mutex_lock(&memBlockListMutex);
        }

        // cut the memBlock out of the memBlock list
        if (ptr->nextBlock != NULL) {
            ptr->nextBlock->previousBlock = ptr->previousBlock;
        }
        if (ptr->previousBlock != NULL) {
            ptr->previousBlock->nextBlock = ptr->nextBlock;
        }
        if (lastMemBlockAllocated == ptr) {
            lastMemBlockAllocated = ptr->nextBlock;
        }

        if (safeThreads) {
            pthread_mutex_unlock(&memBlockListMutex);
        }

        #ifdef PS_MEM_USE_RECYCLE
        // do we recycle?
        int bin = getRecycleBin(ptr->userMemorySize);
        if (bin < N_RECYCLE_BINS && recycleBinNums[bin] < MAX_RECYCLE) {
            ptr->refCounter = 0;
            recyclePush(ptr, bin);
        } else
            #endif  // #ifdef PS_MEM_USE_RECYCLE
            {
                // memory is larger than I want to recycle.
                #ifdef PS_MEM_DEBUG
                (void)p_psRealloc(vptr, 0, file, lineno);
                ptr->previousBlock = NULL;
                ptr->nextBlock = deadBlockList;
                if (deadBlockList != NULL) {
                deadBlockList->previous = ptr;
            }
        deadBlockList = ptr;
        #else // #ifdef PS_MEM_DEBUG

        if (safeThreads) {
            pthread_mutex_destroy(&ptr->refCounterMutex);
            }
            free(ptr);
            #endif // #else - #ifdef PS_MEM_DEBUG

        }

        vptr = NULL;                       // since we freed it, make sure we return NULL.
    }

    return vptr;
}

void psMemSetDeallocator(psPtr ptr,
                         psFreeFunc freeFunc)
{
    if (ptr == NULL) {
        return;
    }

    psMemBlock* PTR = ((psMemBlock* ) ptr) - 1;

    if (checkMemBlock(PTR, __func__) != 0) {
        memProblemCallback(PTR, __func__, __LINE__);
    }

    PTR->freeFunc = freeFunc;

}

psFreeFunc psMemGetDeallocator(const psPtr ptr)
{
    if (ptr == NULL) {
        return NULL;
    }

    psMemBlock* PTR = ((psMemBlock* ) ptr) - 1;

    if (checkMemBlock(PTR, __func__) != 0) {
        memProblemCallback(PTR, __func__, __LINE__);
    }

    return PTR->freeFunc;
}

bool psMemCheckType(psDataType type,
                    psPtr ptr)
{
    PS_ASSERT_PTR(ptr, false);

    switch(type) {
    case PS_DATA_ARRAY:
        if ( psMemCheckArray(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_BITSET:
        if ( psMemCheckBitSet(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_CUBE:
        if ( psMemCheckCube(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_FITS:
        if ( psMemCheckFits(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_HASH:
        if ( psMemCheckHash(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_HISTOGRAM:
        if ( psMemCheckHistogram(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_IMAGE:
        if ( psMemCheckImage(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_KERNEL:
        if ( psMemCheckKernel(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_LINE:
        if ( psMemCheckLine(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_LIST:
        if ( psMemCheckList(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_LOOKUPTABLE:
        if ( psMemCheckLookupTable(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_METADATA:
        if ( psMemCheckMetadata(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_METADATAITEM:
        if ( psMemCheckMetadataItem(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_MINIMIZATION:
        if ( psMemCheckMinimization(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_PIXELS:
        if ( psMemCheckPixels(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_PLANE:
        if ( psMemCheckPlane(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_PLANEDISTORT:
        if ( psMemCheckPlaneDistort(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_PLANETRANSFORM:
        if ( psMemCheckPlaneTransform(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_POLYNOMIAL1D:
        if ( psMemCheckPolynomial1D(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_POLYNOMIAL2D:
        if ( psMemCheckPolynomial2D(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_POLYNOMIAL3D:
        if ( psMemCheckPolynomial3D(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_POLYNOMIAL4D:
        if ( psMemCheckPolynomial4D(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_PROJECTION:
        if ( psMemCheckProjection(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_REGION:
        if ( psMemCheckRegion(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_SCALAR:
        if ( psMemCheckScalar(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_SPHERE:
        if ( psMemCheckSphere(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_SPHEREROT:
        if ( psMemCheckSphereRot(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_SPLINE1D:
        if ( psMemCheckSpline1D(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_STATS:
        if ( psMemCheckStats(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_STRING:
        if ( psMemCheckString(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_TIME:
        if ( psMemCheckTime(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    case PS_DATA_VECTOR:
        if ( psMemCheckVector(ptr) )
            return true;
        else {
            psError(PS_ERR_BAD_PARAMETER_VALUE, false,
                    "Incorrect pointer.  Datatypes do not match.\n");
            break;
        }
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                "Invalid datatype specified.\n");
    }
    return false;
}

bool psMemSetThreadSafety(bool safe)
{
    bool out = safeThreads;
    safeThreads = safe;
    return out;
}

bool psMemGetThreadSafety(void)
{
    return safeThreads;
}

bool p_psMemGetPersistent(psPtr vptr)
{
    if (vptr == NULL) {
        return NULL;
    }

    psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;

    if (checkMemBlock(ptr, __func__) != 0) {
        memProblemCallback(ptr, __func__, __LINE__);
    }

    return ptr->persistent;
}

void p_psMemSetPersistent(psPtr vptr,
                          bool value)
{
    if (vptr == NULL) {
        return;
    }

    psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;

    if (checkMemBlock(ptr, __func__) != 0) {
        memProblemCallback(ptr, __func__, __LINE__);
    }

    ptr->persistent = value;
}

/************************************************************************************************************/
/*
 * Return the total amount of memory owned by psLib; if non-NULL also provide a breakdown
 * into recyclable, allocated, and allocated-and-persistent
 *
 * It would be simple enough to fix this code to return an array of structs to describe
 * the insides of the allocator rather than the printf used here.
 */
size_t psMemStats(const bool print, // print details as they're found?
                  size_t *allocated, // memory that's currently allocated (but not persistent)
                  size_t *persistent, // persistent memory that's currently allocated
                  size_t *freelist) // memory that's waiting to be recycled
{
    const size_t overhead = sizeof(psMemBlock) + sizeof(psPtr); // overhead on each allocation

    if (print) {
        printf("Type       %6s  %10s %8s\n", "size", "nByte", "nBlock");
    }

    if (safeThreads) {
        pthread_mutex_lock(&memBlockListMutex);
    }
    /*
     * All memory that's currently allocated, whether persistent or not
     */
    size_t allocated_s, persistent_s;
    if (allocated == NULL) {
        allocated = &allocated_s;
    }
    if (persistent == NULL) {
        persistent = &persistent_s;
    }

    size_t alloc = 0, persist = 0;
    size_t nalloc = 0, npersist = 0;
    for (psMemBlock* ptr = lastMemBlockAllocated; ptr != NULL; ptr = ptr->nextBlock) {
        assert (ptr->refCounter > 0);

        if (ptr->persistent) {
            npersist++;
            persist += ptr->userMemorySize + overhead;
        } else {
            nalloc++;
            alloc += ptr->userMemorySize + overhead;
        }
    }
    *allocated = alloc;
    *persistent = persist;

    if (print) {
        printf("Allocated  %6s  %10zd %8zd\n", "", alloc, nalloc);
        printf("Persistent %6s  %10zd %8zd\n", "", persist, npersist);
    }
    /*
     * Count the memory on the free list
     */
    size_t freelist_s;
    if (freelist == NULL) {
        freelist = &freelist_s;
    }

    *freelist = 0;
    int nblock_tot = 0;
    #ifdef PS_MEM_USE_RECYCLE

    for (int i = 0; recycleBinSize[i] < P_PS_LARGE_BLOCK_SIZE; i++) {
        size_t bin_contents = 0;
        size_t nblock = 0;
        for (const psMemBlock *ptr = recycleMemBlockList[i]; ptr != NULL; ptr = ptr->nextBlock) {
            nblock++;
            bin_contents += ptr->userMemorySize + overhead;
        }
        *freelist += bin_contents;
        nblock_tot += nblock;

        if (print) {
            printf("Freelist   %6d  %10d %8d\n", recycleBinSize[i], bin_contents, nblock);
        }
    }
    #endif // #ifdef PS_MEM_USE_RECYCLE

    if (print) {
        printf("Freelist   %6s  %10zd %8d\n", "", *freelist, nblock_tot);
    }


    if (safeThreads) {
        pthread_mutex_unlock(&memBlockListMutex);
    }

    return *freelist + *allocated + *persistent;
}
