/** @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.70 $ $Name: not supported by cvs2svn $
*  @date $Date: 2006-02-28 02:53:03 $
*
*  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 "psMemory.h"
#include "psError.h"
#include "psAbort.h"
#include "psLogMsg.h"

#include "psBitSet.h"
//#include "psArray.h"
//#include "psImage.h"
//#include "psList.h"
//#include "psLookupTable.h"
//#include "psHash.h"

//#include "psMetadata.h"
#include "psFits.h"
#include "psPixels.h"
//#include "psScalar.h"
//#include "psVector.h"
//#include "psTime.h"
//#include "psCoord.h"
#include "psSphereOps.h"
//#include "psStats.h"
#include "psMinimizeLMM.h"
#include "psImageConvolve.h"
#include "psTime.h"

#include "psErrorText.h"

#define P_PS_MEMMAGIC (psPtr )0xdeadbeef   // Magic number in psMemBlock header

#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;

static pthread_mutex_t recycleMemBlockListMutex = PTHREAD_MUTEX_INITIALIZER;

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

static psS32 recycleBins = 13;
static psS32 recycleBinSize[14] = {
                                      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 psMemBlock* recycleMemBlockList[13] = {
            NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
        };

#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)
{
    psPtr ptr = NULL;
    if (safeThreads) {
        pthread_mutex_lock(&recycleMemBlockListMutex);
    }
    psS32 level = recycleBins - 1;

    while (level >= 0 && ptr == NULL) {
        while (recycleMemBlockList[level] != NULL && ptr == NULL) {
            psMemBlock* old = recycleMemBlockList[level];

            recycleMemBlockList[level] = recycleMemBlockList[level]->nextBlock;
            free(old);
            ptr = malloc(size);
        }
        level--;
    }

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

    return ptr;
}

/*
 * 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,
                PS_ERRORTEXT_psMemory_MULTIPLE_FREE,
                (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,
                PS_ERRORTEXT_psMemory_NULL_BLOCK);
        return 1;
    }

    if (m->refCounter == 0) {
        // using an unreferenced block of memory, are you?
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                PS_ERRORTEXT_psMemory_DEREF_BLOCK_USE,
                (unsigned long)m->id);
        return 1;
    }

    if (m->startblock != P_PS_MEMMAGIC || m->endblock != P_PS_MEMMAGIC) {
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                PS_ERRORTEXT_psMemory_UNDERFLOW,
                (unsigned long)m->id);
        return 1;
    }
    if (*(psPtr *)((int8_t *) (m + 1) + m->userMemorySize) != P_PS_MEMMAGIC) {
        psError(PS_ERR_MEMORY_CORRUPTION, true,
                PS_ERRORTEXT_psMemory_OVERFLOW,
                (unsigned long)m->id);
        return 1;
    }

    return 0;
}

/*
 * 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;

    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)
{
    psMemId id;

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

    id = memid + 1;

    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;
}

psPtr p_psAlloc(size_t size,
                const char *file,
                unsigned int lineno)
{

    psMemBlock* ptr = NULL;

    size = (size < recycleBinSize[0]) ? recycleBinSize[0] : size; // set the minimum size to allocate

    // memory is of the size I want to bother recycling?
    if (size < P_PS_LARGE_BLOCK_SIZE) {
        // find the bin we need.
        psS32 level = 0;

        while (size > recycleBinSize[level]) {
            level++;
        }
        // Are we in one of the bins
        if (level < recycleBins) {

            size = recycleBinSize[level];  // round-up size to next sized bin.

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

            if (recycleMemBlockList[level] != NULL) {
                ptr = recycleMemBlockList[level];
                recycleMemBlockList[level] = ptr->nextBlock;
                if (recycleMemBlockList[level] != NULL) {
                    recycleMemBlockList[level]->previousBlock = NULL;
                }
                size = ptr->userMemorySize;
            }

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

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

        if (ptr == NULL) {
            ptr = memExhaustedCallback(size);
            if (ptr == NULL) {
                psAbort(__func__, "Failed to allocate %u 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 = false;
    *(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)
{
    size = (size < recycleBinSize[0]) ? recycleBinSize[0] : size; // set the minimum size to allocate

    if (vptr == NULL) {
        return p_psAlloc(size, file, lineno);
    } else {
        psMemBlock* ptr = ((psMemBlock* ) vptr) - 1;
        psBool isBlockLast = false;

        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);
        }

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

        isBlockLast = (ptr == lastMemBlockAllocated);

        ptr = (psMemBlock* ) realloc(ptr, sizeof(psMemBlock) + size + sizeof(psPtr ));

        if (ptr == NULL) {
            ptr = memExhaustedCallback(size);
            if(ptr == NULL) {
                psAbort(__func__, "Failed to reallocate %ld 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__,PS_ERRORTEXT_psMemory_MULTIPLE_FREE,
                (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(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);
        }

        // do we need to recycle?
        if (ptr->userMemorySize < P_PS_LARGE_BLOCK_SIZE) {

            psS32 level = 1;

            while (ptr->userMemorySize >= recycleBinSize[level]) {
                level++;
            }
            level--;

            ptr->refCounter = 0;
            ptr->previousBlock = NULL;

            if (safeThreads) {
                pthread_mutex_lock(&recycleMemBlockListMutex);
            }
            ptr->nextBlock = recycleMemBlockList[level];
            if (recycleMemBlockList[level] != NULL) {
                recycleMemBlockList[level]->previousBlock = ptr;
            }
            recycleMemBlockList[level] = ptr;
            if (safeThreads) {
                pthread_mutex_unlock(&recycleMemBlockListMutex);
            }

        } else {
            // 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)
{
    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_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_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_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 psMemThreadSafety(bool safe)
{
    bool out = safeThreads;
    safeThreads = safe;
    return out;
}

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;
}
