/** @file  psLookupTable.c
*
*  @brief This file defines the structure and functions for table lookups.
*
*  @ingroup dataIO
*
*  @author Ross Harman, MHPCC
*
*  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-04-08 17:58:57 $
*
*  Copyright 2004-5 Maui High Performance Computing Center, University of Hawaii
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
//#ifdef DARWIN
#undef __STRICT_ANSI__
//#endif
#include <stdlib.h>
//#ifdef DARWIN
#define __STRICT_ANSI__
//#endif
#include <math.h>
#include <stdlib.h>

#include "psMemory.h"
#include "psString.h"
#include "psError.h"
#include "psLookupTable.h"
#include "psFileUtilsErrors.h"
#include "psConstants.h"

/******************************************************************************/
/*  DEFINE STATEMENTS                                                         */
/******************************************************************************/

/** Maximum size of a string */
#define MAX_STRING_LENGTH 256

/******************************************************************************/
/*  TYPE DEFINITIONS                                                          */
/******************************************************************************/

// None

/*****************************************************************************/
/*  GLOBAL VARIABLES                                                         */
/*****************************************************************************/

// None

/*****************************************************************************/
/*  FILE STATIC VARIABLES                                                    */
/*****************************************************************************/

// None

/*****************************************************************************/
/*  FUNCTION IMPLEMENTATION - LOCAL                                          */
/*****************************************************************************/

static bool ignoreLine(char *inString);
static char *cleanString(char *inString, int sLen);
static char* getToken(char **inString, char *delimiter, psParseErrorType *status);
static psU8 parseU8(char *inString, psParseErrorType *status);
static psS8 parseS8(char *inString, psParseErrorType *status);
static psU16 parseU16(char *inString, psParseErrorType *status);
static psS16 parseS16(char *inString, psParseErrorType *status);
static psU32 parseU32(char *inString, psParseErrorType *status);
static psS32 parseS32(char *inString, psParseErrorType *status);
static psU64 parseU64(char *inString, psParseErrorType *status);
static psS64 parseS64(char *inString, psParseErrorType *status);
static psF32 parseF32(char *inString, psParseErrorType *status);
static psF64 parseF64(char *inString, psParseErrorType *status);
static void parseValue(psVector *vec, psU64 index, char* strValue, psParseErrorType *status);
static void lookupTableFree(psLookupTable* table);

/** Determines if a line is blank (whitespace only) or a commentline. It returns true if so. The input string
 *  must be null terminated. */
static bool ignoreLine(char *inString)
{
    while(*inString!='\0' && *inString!='#') {
        if(!isspace(*inString)) {
            return false;
        }
        inString++;
    }

    return true;
}


/** Removes leading and trailing whitespace and # characters from a string. The cleaned string is a new null
 *  terminated copy of the original input string. */
static char *cleanString(char *inString, int sLen)
{
    char *ptrB = NULL;
    char *ptrE = NULL;
    char *cleaned = NULL;


    ptrB = inString;

    /* Skip over leading # or whitespace */
    while (isspace(*ptrB) || *ptrB=='#') {
        ptrB++;
    }

    /* Skip over trailing whitespace, null terminators, and # characters */
    ptrE = inString + sLen;
    while(isspace(*ptrE) || *ptrE=='\0' || *ptrE=='#') {
        ptrE--;
    }

    // Length, sLen, does not include '\0'
    sLen = ptrE - ptrB + 1;

    // Adds '\0' to end of string and +1 to sLen
    cleaned = psStringNCopy(ptrB, sLen);

    return cleaned;
}


/** Returns cleaned token based on delimiter, but not including delimiter. Also changes the pointer location
 * the beginning of the string. Tokens are newly allocated null terminated strings. */
static char* getToken(char **inString, char *delimiter, psParseErrorType *status)
{
    char *cleanToken = NULL;
    int sLen = 0;


    // Skip over leading whitespace
    while(isspace(**inString)) {
        (*inString)++;
    }

    // Length of token, not including delimiter
    sLen = strcspn(*inString, delimiter);
    if(sLen) {

        // Create new, cleaned, and null terminated token
        cleanToken = cleanString(*inString, sLen);

        // Move to end of token
        (*inString) += sLen;
    } else if(**inString!='\0' && sLen==0) {
        *status = PS_PARSE_ERROR_GENERAL;
    }

    return cleanToken;
}

/** Returns single parsed value as a psU8. The input string must be cleaned and null terminated. */
static psU8 parseU8(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psU8 value = 0.0;


    value = (psU8)strtoul(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psS8. The input string must be cleaned and null terminated. */
static psS8 parseS8(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psS8 value = 0.0;


    value = (psS8)strtol(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psU16. The input string must be cleaned and null terminated. */
static psU16 parseU16(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psU16 value = 0.0;


    value = (psU16)strtoul(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psS16. The input string must be cleaned and null terminated. */
static psS16 parseS16(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psS16 value = 0.0;


    value = (psS16)strtol(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psU32. The input string must be cleaned and null terminated. */
static psU32 parseU32(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psU32 value = 0.0;


    value = (psU32)strtoul(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psS32. The input string must be cleaned and null terminated. */
static psS32 parseS32(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psS32 value = 0.0;


    value = (psS32)strtol(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psU64. The input string must be cleaned and null terminated. */
static psU64 parseU64(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psU64 value = 0.0;


    value = (psU64)strtoull(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psS64. The input string must be cleaned and null terminated. */
static psS64 parseS64(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psS64 value = 0.0;


    value = (psS64)strtoll(inString, &end, 0);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psF32. The input string must be cleaned and null terminated. */
static psF32 parseF32(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psF32 value = 0.0;


    value = (psF32)strtof(inString, &end);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a psF64. The input string must be cleaned and null terminated. */
static psF64 parseF64(char *inString, psParseErrorType *status)
{
    char *end = NULL;
    psF64 value = 0.0;


    value = (psF64)strtod(inString, &end);
    if(*end != '\0') {
        *status = PS_PARSE_ERROR_VALUE;
    } else if(inString==end) {
        *status = PS_PARSE_ERROR_VALUE;
    }

    return value;
}

/** Returns single parsed value as a double precision number. The input string must be cleaned and null
 * terminated. */
static void parseValue(psVector *vec, psU64 index, char* strValue, psParseErrorType *status)
{
    psElemType type;


    if(vec == NULL) {
        *status = 1;
        return;
    }

    type = vec->type.type;

    switch(type) {
    case PS_TYPE_U8:
        vec->data.U8[index] = parseU8(strValue, status);
        break;
    case PS_TYPE_S8:
        vec->data.S8[index] = parseS8(strValue, status);
        break;
    case PS_TYPE_U16:
        vec->data.U16[index] = parseU16(strValue, status);
        break;
    case PS_TYPE_S16:
        vec->data.S16[index] = parseS16(strValue, status);
        break;
    case PS_TYPE_U32:
        vec->data.U32[index] = parseU32(strValue, status);
        break;
    case PS_TYPE_S32:
        vec->data.S32[index] = parseS32(strValue, status);
        break;
    case PS_TYPE_U64:
        vec->data.U64[index] = parseU64(strValue, status);
        break;
    case PS_TYPE_S64:
        vec->data.S64[index] = parseS64(strValue, status);
        break;
    case PS_TYPE_F32:
        vec->data.F32[index] = parseF32(strValue, status);
        break;
    case PS_TYPE_F64:
        vec->data.F64[index] = parseF64(strValue, status);
        break;
    default:
        *status = PS_PARSE_ERROR_TYPE;
    }

    return;
}

static psParseErrorType printError(psU64 lineCount, char* badText, psParseErrorType status)
{
    switch(status) {
    case PS_PARSE_ERROR_VALUE:
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_PARSE_VALUE, badText, lineCount);
        break;
    case PS_PARSE_ERROR_TYPE:
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_PARSE_TYPE, badText, lineCount);
        break;
    case PS_PARSE_ERROR_GENERAL:
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_PARSE_GENERAL, badText, lineCount);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_INVALID_TYPE, badText, lineCount);
    }

    return PS_LOOKUP_SUCCESS;
}

static void lookupTableFree(psLookupTable* table)
{
    if (table == NULL) {
        return;
    }

    psFree(table->values);
    psFree(table->index);
    psFree((char*)table->fileName);
}

/*****************************************************************************/
/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
/*****************************************************************************/

psLookupTable* psLookupTableAlloc(const char *fileName, psF64 validFrom, psF64 validTo)
{
    psLookupTable *outTable = NULL;


    // Can't read table if you don't know its name
    PS_PTR_CHECK_NULL(fileName,NULL);

    // Allocate lookup table
    outTable = (psLookupTable*)psAlloc(sizeof(psLookupTable));

    // Set deallocator
    psMemSetDeallocator(outTable, (psFreeFcn)lookupTableFree);

    // Allocate and set metadata item comment
    outTable->fileName = psStringCopy(fileName);

    // Number of table rows and columns. Automatically resized by table read.
    outTable->numRows = 0;
    outTable->numCols = 0;

    // Valid ranges. Automatically set by table read if both zero.
    outTable->validFrom = validFrom;
    outTable->validTo = validTo;

    // Vector of independent index values. Filled by table read.
    outTable->index = NULL;

    // Array of dependent table values corresponding to index values. Filled by table read.
    outTable->values = NULL;

    return outTable;
}

#define UPDATE_VALID_TO_FROM(TABLE)                                             \
switch (TABLE->index->type.type) {                                              \
case PS_TYPE_U8:                                                                \
    TABLE->validFrom = (psF64)TABLE->index->data.U8[0];                         \
    TABLE->validTo   = (psF64)TABLE->index->data.U8[TABLE->index->nalloc-1];    \
    break;                                                                      \
case PS_TYPE_S8:                                                                \
    TABLE->validFrom = (psF64)TABLE->index->data.S8[0];                         \
    TABLE->validTo   = (psF64)TABLE->index->data.S8[TABLE->index->nalloc-1];    \
    break;                                                                      \
case PS_TYPE_U16:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.U16[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.U16[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_S16:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.S16[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.S16[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_U32:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.U32[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.U32[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_S32:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.S32[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.S32[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_U64:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.U64[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.U64[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_S64:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.S64[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.S64[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_F32:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.F32[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.F32[TABLE->index->nalloc-1];   \
    break;                                                                      \
case PS_TYPE_F64:                                                               \
    TABLE->validFrom = (psF64)TABLE->index->data.F64[0];                        \
    TABLE->validTo   = (psF64)TABLE->index->data.F64[TABLE->index->nalloc-1];   \
    break;                                                                      \
default:                                                                        \
    TABLE->validFrom = (psF64)0;                                                \
    TABLE->validTo   = (psF64)0;                                                \
    break;                                                                      \
}

#define COPY_VECTOR_VALUES(VEC_OUT,INDEX_OUT,VEC_IN,INDEX_IN)                              \
switch(((psVector*)(VEC_IN))->type.type) {                                                 \
case PS_TYPE_U8:                                                                           \
    ((psVector*)VEC_OUT)->data.U8[INDEX_OUT] = ((psVector*)VEC_IN)->data.U8[INDEX_IN];     \
    break;                                                                                 \
case PS_TYPE_U16:                                                                          \
    ((psVector*)VEC_OUT)->data.U16[INDEX_OUT] = ((psVector*)VEC_IN)->data.U16[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_U32:                                                                          \
    ((psVector*)VEC_OUT)->data.U32[INDEX_OUT] = ((psVector*)VEC_IN)->data.U32[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_U64:                                                                          \
    ((psVector*)VEC_OUT)->data.U64[INDEX_OUT] = ((psVector*)VEC_IN)->data.U64[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_S8:                                                                           \
    ((psVector*)VEC_OUT)->data.S8[INDEX_OUT] = ((psVector*)VEC_IN)->data.S8[INDEX_IN];     \
    break;                                                                                 \
case PS_TYPE_S16:                                                                          \
    ((psVector*)VEC_OUT)->data.S16[INDEX_OUT] = ((psVector*)VEC_IN)->data.S16[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_S32:                                                                          \
    ((psVector*)VEC_OUT)->data.S32[INDEX_OUT] = ((psVector*)VEC_IN)->data.S32[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_S64:                                                                          \
    ((psVector*)VEC_OUT)->data.S64[INDEX_OUT] = ((psVector*)VEC_IN)->data.S64[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_F32:                                                                          \
    ((psVector*)VEC_OUT)->data.F32[INDEX_OUT] = ((psVector*)VEC_IN)->data.F32[INDEX_IN];   \
    break;                                                                                 \
case PS_TYPE_F64:                                                                          \
    ((psVector*)VEC_OUT)->data.F64[INDEX_OUT] = ((psVector*)VEC_IN)->data.F64[INDEX_IN];   \
    break;                                                                                 \
default:                                                                                   \
    break;                                                                                 \
}


psLookupTable* psLookupTableRead(psLookupTable *table)
{
    bool typeLine = true;
    char *line = NULL;
    char *strType = NULL;
    char *strValue = NULL;
    char *linePtr = NULL;
    psParseErrorType status = PS_PARSE_SUCCESS;
    psParseErrorType lineStatus = PS_PARSE_SUCCESS;
    psU64 lineCount = 0;
    psU64 numRows = 0;
    psU64 numCols = 0;
    psU64 failedLines = 0;
    FILE *fp = NULL;
    psElemType elemType;
    psVector *indexVec = NULL;
    psVector *valuesVec = NULL;
    psArray *values = NULL;
    psBool sortIndex = false;

    // Check for NULL input table
    PS_PTR_CHECK_NULL(table,NULL);

    // Check for input table with NULL file name
    PS_PTR_CHECK_NULL(table->fileName,NULL);

    // Open table file specified by table->fileName
    if((fp=fopen(table->fileName, "r")) == NULL) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_FILE_NOT_FOUND,
                table->fileName);
        return table;
    }

    // Initialize vector pointers
    indexVec = table->index;
    values = table->values = psArrayAlloc(10);
    values->n = 0;

    // Create reusable line for continuous read
    line = (char*)psAlloc(MAX_STRING_LENGTH*sizeof(char));

    // Loop through file to get numRows, numCols, and column data types
    while((fgets(line, MAX_STRING_LENGTH, fp) != NULL) && (lineStatus == PS_PARSE_SUCCESS)) {

        // Initialize variables for new line
        linePtr = line;
        lineCount++;

        // If line is not a comment or blank, then extract data
        if(!ignoreLine(linePtr)) {

            if(typeLine == true) {

                // Determine column types from first line in data file after comments
                while((strType=getToken(&linePtr," ",&status))) {
                    numCols++;
                    typeLine = false;
                    if(!strncmp(strType, "psU8", 4)) {
                        elemType = PS_TYPE_U8;
                    } else if(!strncmp(strType, "psS8", 4)) {
                        elemType = PS_TYPE_S8;
                    } else if(!strncmp(strType, "psU16", 5)) {
                        elemType = PS_TYPE_U16;
                    } else if(!strncmp(strType, "psS16", 5)) {
                        elemType = PS_TYPE_S16;
                    } else if(!strncmp(strType, "psU32", 5)) {
                        elemType = PS_TYPE_U32;
                    } else if(!strncmp(strType, "psS32", 5)) {
                        elemType = PS_TYPE_S32;
                    } else if(!strncmp(strType, "psU64", 5)) {
                        elemType = PS_TYPE_U64;
                    } else if(!strncmp(strType, "psS64", 5)) {
                        elemType = PS_TYPE_S64;
                    } else if(!strncmp(strType, "psF32", 5)) {
                        elemType = PS_TYPE_F32;
                    } else if(!strncmp(strType, "psF64", 5)) {
                        elemType = PS_TYPE_F64;
                    } else {
                        status = PS_PARSE_ERROR_TYPE;
                    }

                    // Realloc number of columns as you go
                    psVector* vec = psVectorAlloc(0, elemType);
                    if(numCols == 1) {
                        indexVec = vec;
                    } else {
                        psArrayAdd(values,0,vec);
                        psFree(vec);
                    }

                    if(status) {
                        printError(lineCount, strValue, status);
                        failedLines++;
                        lineStatus = status;
                        status = PS_PARSE_SUCCESS;
                    }
                    psFree(strType);
                }
            } else {
                // Parse and add values to all columns
                numRows++;
                numCols = 0;
                while((strValue=getToken(&linePtr," ",&status))) {
                    numCols++;

                    // Realloc number of rows as you go
                    if(numCols == 1) {
                        sortIndex = false;
                        indexVec = psVectorRecycle(indexVec, numRows, indexVec->type.type);
                        parseValue(indexVec, numRows-1, strValue, &status);
                    } else {
                        valuesVec = values->data[numCols-2];
                        valuesVec = psVectorRecycle(valuesVec, numRows, valuesVec->type.type);
                        parseValue(valuesVec, numRows-1, strValue, &status);
                    }

                    if(status) {
                        printError(lineCount, strValue, status);
                        failedLines++;
                        lineStatus = status;
                        status = PS_PARSE_SUCCESS;
                    }
                    psFree(strValue);
                } // end while
            } // end else
        } // if ignoreLine
    } // end while

    psFree(line);

    // Set table for return
    table->numRows = numRows;
    table->numCols = numCols-1;
    table->index = indexVec;
    table->values = values;

    // Set table values to indicate error detected during the read
    if(lineStatus) {
        table->numRows = 0;
        table->numCols = 0;
        table->validFrom = 0;
        table->validTo = 0;
        psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psLookupTable_TABLE_INVALID);
    } else {
        // Check if index vector needs to be sorted
        psVector* sortedIndex = psVectorAlloc(table->numRows,PS_TYPE_U32);
        sortedIndex = psVectorSortIndex(sortedIndex,table->index);
        for(psS32 i = 0; i < numRows; i++ ) {
            if(sortedIndex->data.U32[i] != i) {
                sortIndex = true;
                break;
            }
        }
        // Check if it is necessary to sort value vectors
        if(sortIndex) {
            // Allocate new index vector
            psVector* newIndexVector = psVectorAlloc(numRows,indexVec->type.type);
            // Allocate new value vectors
            psArray* newValueArray = psArrayAlloc(numCols-1);
            for(psS32 j = 0; j < numCols-1; j++) {
                psS32 type = ((psVector*)(table->values->data[j]))->type.type;
                newValueArray->data[j] = psVectorAlloc(numRows,type);
            }
            for(psS32 i = 0; i < numRows; i++) {
                // Populate new index vector
                psU32 sortIndex = sortedIndex->data.U32[i];
                COPY_VECTOR_VALUES(newIndexVector,i, indexVec,sortIndex)
                // For every column populate new value vectors
                for(psS32 j=0; j < numCols-1; j++) {
                    COPY_VECTOR_VALUES(newValueArray->data[j],i,table->values->data[j], sortIndex)
                }
            }
            // Free old index vector
            psFree(table->index);
            // Free old value vectors
            psFree(table->values);
            // Assign new vector value to table
            table->index = newIndexVector;
            // Assign new value vectors to table array
            table->values = newValueArray;
        }
        psFree(sortedIndex);
        // Update the validTo and validFrom
        UPDATE_VALID_TO_FROM(table)
    }

    fclose(fp);

    return table;
}

#define CONVERT_VALUE_TO_F64(VECTOR,INDEX,RESULT)     \
switch(VECTOR->type.type) {                           \
case PS_TYPE_U8:                                      \
    RESULT = (psF64)VECTOR->data.U8[INDEX];           \
    break;                                            \
case PS_TYPE_U16:                                     \
    RESULT = (psF64)VECTOR->data.U16[INDEX];          \
    break;                                            \
case PS_TYPE_U32:                                     \
    RESULT = (psF64)VECTOR->data.U32[INDEX];          \
    break;                                            \
case PS_TYPE_U64:                                     \
    RESULT = (psF64)VECTOR->data.U64[INDEX];          \
    break;                                            \
case PS_TYPE_S8:                                      \
    RESULT = (psF64)VECTOR->data.S8[INDEX];           \
    break;                                            \
case PS_TYPE_S16:                                     \
    RESULT = (psF64)VECTOR->data.S16[INDEX];          \
    break;                                            \
case PS_TYPE_S32:                                     \
    RESULT = (psF64)VECTOR->data.S32[INDEX];          \
    break;                                            \
case PS_TYPE_S64:                                     \
    RESULT = (psF64)VECTOR->data.S64[INDEX];          \
    break;                                            \
case PS_TYPE_F32:                                     \
    RESULT = (psF64)VECTOR->data.F32[INDEX];          \
    break;                                            \
case PS_TYPE_F64:                                     \
    RESULT = VECTOR->data.F64[INDEX];                 \
    break;                                            \
default:                                              \
    RESULT = NAN;                                     \
    break;                                            \
}


#define CHECK_LOWER_UPPER_BOUND(TABLE,INDEX,COLUMN)                                \
switch (TABLE->index->type.type) {                                                 \
case PS_TYPE_U8:                                                                   \
    if( (psU8)index < TABLE->index->data.U8[0] ) {                                 \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psU8)index > TABLE->index->data.U8[numRows-1]) {                          \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_U16:                                                                  \
    if( (psU16)index < TABLE->index->data.U16[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psU16)index > TABLE->index->data.U16[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_U32:                                                                  \
    if( (psU32)index < TABLE->index->data.U32[0]) {                                \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if ( (psU32)index > TABLE->index->data.U32[numRows-1] ) {                      \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_U64:                                                                  \
    if( (psU64)index < TABLE->index->data.U64[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psU64)index > TABLE->index->data.U64[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_S8:                                                                   \
    if( (psS8)index < TABLE->index->data.S8[0] ) {                                 \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psS8)index > TABLE->index->data.S8[numRows-1] ) {                         \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_S16:                                                                  \
    if( (psS16)index < TABLE->index->data.S16[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psS16)index > TABLE->index->data.S16[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_S32:                                                                  \
    if( (psS32)index < TABLE->index->data.S32[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psS32)index > TABLE->index->data.S32[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_S64:                                                                  \
    if( (psS64)index < TABLE->index->data.S64[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psS64)index > TABLE->index->data.S64[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_F32:                                                                  \
    if( (psF32)index < TABLE->index->data.F32[0] ) {                               \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( (psF32)index > TABLE->index->data.F32[numRows-1] ) {                       \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
case PS_TYPE_F64:                                                                  \
    if( index < TABLE->index->data.F64[0] ) {                                      \
        *status = PS_LOOKUP_PAST_TOP;                                              \
    }                                                                              \
    if( index > TABLE->index->data.F64[numRows-1] ) {                              \
        *status = PS_LOOKUP_PAST_BOTTOM;                                           \
    }                                                                              \
    break;                                                                         \
default:                                                                           \
    *status = PS_LOOKUP_ERROR;                                                     \
    return NAN;                                                                    \
    break;                                                                         \
}                                                                                  \
if(*status == PS_LOOKUP_PAST_TOP) {                                                \
    CONVERT_VALUE_TO_F64(((psVector*)(TABLE->values->data[COLUMN])),0,out)         \
    return out;                                                                    \
} else if (*status == PS_LOOKUP_PAST_BOTTOM) {                                     \
    CONVERT_VALUE_TO_F64(((psVector*)(TABLE->values->data[COLUMN])),numRows-1,out) \
    return out;                                                                    \
}


psF64 psLookupTableInterpolate(psLookupTable *table, psF64 index, psU64 column, psLookupStatusType *status)
{
    psU64 hiIdx = 0;
    psU64 loIdx = 0;
    psU64 numRows = 0;
    psU64 numCols = 0;
    psF64 out = 0.0;
    psF64 denom = 0.0;
    psF64 convertVal = 0.0;
    psF64 tempVal = 0.0;
    psVector *indexVec = NULL;
    psVector *valuesVec = NULL;
    psArray *values = NULL;

    // Error checks
    // Set status to error prior to check since if checks fails it will immediately return
    PS_PTR_CHECK_NULL(status,NAN);
    *status = PS_LOOKUP_ERROR;
    PS_PTR_CHECK_NULL(table,NAN);
    indexVec = table->index;
    values = table->values;
    numRows = table->numRows;
    numCols = table->numCols;
    PS_PTR_CHECK_NULL(indexVec,NAN);
    PS_PTR_CHECK_NULL(values,NAN);
    PS_INT_CHECK_EQUALS(numRows, 0,NAN);
    PS_INT_CHECK_EQUALS(numCols, 0,NAN);
    PS_INT_CHECK_RANGE(column, 0, (int)(numCols-1), NAN);

    valuesVec = (psVector*)values->data[column];
    PS_PTR_CHECK_NULL(indexVec,NAN);

    // Set status to success since it passed all parameter checks
    *status = PS_LOOKUP_SUCCESS;

    // Verify the index is within the bounds of the table
    CHECK_LOWER_UPPER_BOUND(table,index,column)

    // Find location in table where specified index is between to entries
    CONVERT_VALUE_TO_F64(indexVec, 0, convertVal)
    while(index > convertVal ) {
        hiIdx++;
        if(hiIdx >= numRows) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                    PS_ERRORTEXT_psLookupTable_INTERPOLATE_HIGH, hiIdx);
            *status = PS_LOOKUP_ERROR;
            return NAN;
        }
        CONVERT_VALUE_TO_F64(indexVec, hiIdx, convertVal)
    }

    // Check for negative low index and generate error
    loIdx = hiIdx--;
    if(loIdx < 0) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psLookupTable_INTERPOLATE_LOW, loIdx);
        *status = PS_LOOKUP_ERROR;
        return NAN;
    }

    // Perform linear interpolation to calculate return value
    CONVERT_VALUE_TO_F64(indexVec, hiIdx, denom)
    CONVERT_VALUE_TO_F64(indexVec, loIdx, convertVal);
    denom -= convertVal;
    if(fabs(denom) < FLT_EPSILON) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_DIVIDE_BY_ZERO);
        *status = PS_LOOKUP_ERROR;
        return NAN;
    } else {
        CONVERT_VALUE_TO_F64(valuesVec,hiIdx,tempVal)
        CONVERT_VALUE_TO_F64(valuesVec,loIdx,convertVal)
        tempVal -= convertVal;
        CONVERT_VALUE_TO_F64(indexVec,loIdx,convertVal)
        out = tempVal*(index-convertVal)/denom;
        CONVERT_VALUE_TO_F64(valuesVec,loIdx,convertVal)
        out += convertVal;
    }

    return out;
}

psVector* psLookupTableInterpolateAll(psLookupTable *table, psF64 index, psVector *stats)
{
    psU64 i = 0;
    psU64 numCols = 0;
    psVector *outVector = NULL;
    psLookupStatusType status = PS_LOOKUP_SUCCESS;

    // Error checks
    PS_PTR_CHECK_NULL(table,NULL);
    PS_PTR_CHECK_NULL(stats,NULL);
    numCols = table->numCols;
    PS_INT_CHECK_EQUALS(numCols, 0,NULL);

    outVector = psVectorAlloc(numCols+1, PS_TYPE_F64);

    // Fill vectors with results and status of results
    for(i=0; i<numCols; i++) {
        outVector->data.F64[i] = psLookupTableInterpolate(table, index, i, &status);
        stats->data.U32[i] = status;
    }

    return outVector;
}

