/** @file  psLookupTable.c
*
*  @brief This file defines the structure and functions for table lookups.
*
*  @ingroup fileUtils
*
*  @author Ross Harman, MHPCC
*
*  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
*  @date $Date: 2004-12-16 18:53:02 $
*
*  Copyright 2004 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 "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
    p_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;
}

psLookupTable* psLookupTableRead(psLookupTable *table)
{
    bool typeLine = true;
    char *line = NULL;
    char *strType = NULL;
    char *strValue = NULL;
    char *linePtr = NULL;
    psParseErrorType status = 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;


    // Error checks
    PS_PTR_CHECK_NULL(table,NULL);
    PS_PTR_CHECK_NULL(table->fileName,NULL);
    if((fp=fopen(table->fileName, "r")) == NULL) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_FILE_NOT_FOUND, table->fileName);
        return table;
    }

    indexVec = table->index;
    table->values = psArrayAlloc(1);
    values = table->values;

    // 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) {

        // 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)) != NULL) {
                    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
                    if(numCols == 1) {
                        indexVec = psVectorAlloc(0, elemType);
                    } else {
                        values = psArrayRealloc(values, numCols-1);
                        values->n = values->nalloc;
                        values->data[numCols-2] = psVectorAlloc(0, elemType);
                    }

                    if(status) {
                        status = printError(lineCount, strValue, status);
                        failedLines++;
                    }
                    psFree(strType);
                }
            } else {

                // Parse and add values to all columns
                numRows++;
                numCols = 0;
                while((strValue=getToken(&linePtr, " ", &status)) != NULL) {
                    numCols++;

                    // Realloc number of rows as you go
                    if(numCols == 1) {
                        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) {
                        status = printError(lineCount, strValue, status);
                        failedLines++;
                    }
                    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;

    fclose(fp);

    return table;
}

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;
    psVector *indexVec = NULL;
    psVector *valuesVec = NULL;
    psArray *values = NULL;


    // Error checks
    PS_PTR_CHECK_NULL(table,0.0);
    indexVec = table->index;
    values = table->values;
    numRows = table->numRows;
    numCols = table->numCols;
    PS_PTR_CHECK_NULL(indexVec,0.0);
    PS_PTR_CHECK_NULL(values,0.0);
    PS_INT_CHECK_EQUALS(numRows, 0,0.0);
    PS_INT_CHECK_EQUALS(numCols, 0,0.0);
    PS_INT_CHECK_RANGE(column, 0, numCols-1, 0.0);


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

    if(index<indexVec->data.F64[0] || index<table->validFrom) {

        // Index value past top of table
        *status = PS_LOOKUP_PAST_TOP;
        return valuesVec->data.F64[0];
    } else if(index>indexVec->data.F64[numRows-1] || index>table->validTo) {

        // Index value past bottom of table
        *status = PS_LOOKUP_PAST_BOTTOM;
        return valuesVec->data.F64[numRows-1];
    } else {

        // Interpolation
        while(index > indexVec->data.F64[hiIdx]) {
            hiIdx++;
            if(hiIdx >= numRows) {
                psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_INTERPOLATE_HIGH, hiIdx);
                *status = PS_LOOKUP_ERROR;
                return 0.0;
            }
        }

        loIdx = hiIdx--;
        if(loIdx < 0) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_INTERPOLATE_LOW, loIdx);
            *status = PS_LOOKUP_ERROR;
            return 0.0;
        }

        denom = indexVec->data.F64[hiIdx] - indexVec->data.F64[loIdx];
        if(fabs(denom) < FLT_EPSILON) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psLookupTable_DIVIDE_BY_ZERO);
            *status = PS_LOOKUP_ERROR;
            return 0.0;
        } else {
            out = valuesVec->data.F64[loIdx]+(valuesVec->data.F64[hiIdx]-valuesVec->data.F64[loIdx])
                  *(index-indexVec->data.F64[loIdx])/denom;
        }
    }

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

