/** @file  psMetadataIO.c
*
*  @brief Contains metadata input/output functions.
*
*  This file defines functions to read and write metadata to/from an external file.
*
*  @ingroup Metadata
*
*  @author Ross Harman, MHPCC
*  @author Eric Van Alst, MHPCC
*
*  @version $Revision: 1.48 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-10-01 02:22:15 $
*
*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
*/

#include <fitsio.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "psAbort.h"
#include "psType.h"
#include "psMemory.h"
#include "psError.h"
#include "psString.h"
#include "psList.h"
#include "psHash.h"
#include "psVector.h"
#include "psMetadata.h"
#include "psMetadataConfig.h"
#include "psConstants.h"
#include "psErrorText.h"

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

/** Check for FITS errors */
#define FITS_ERROR(STRING,PS_ERROR)                                                                          \
fits_get_errstatus(status, fitsErr);                                                                         \
psError(PS_ERR_IO,true, STRING, PS_ERROR, fitsErr);                                                          \
status = 0;                                                                                                  \
fits_close_file(fd, &status);                                                                                \
if(status){                                                                                                  \
    fits_get_errstatus(status, fitsErr);                                                                     \
    psError(PS_ERR_IO,true, "Couldn't close FITS file. FITS error: %s", fitsErr);                            \
}                                                                                                            \
status = 0;                                                                                                  \
psFree(output);                                                                                              \
return NULL;

/** Maximum size of a FITS line */
#define FITS_LINE_SIZE 80

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


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

// None

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

// None

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

// None

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

static psMetadata* getMetadataType(char *linePtr);
static psMetadata* setMetadataItem(psMetadata* template, char* linePtr)
;
static void parseLevelInfoFree(p_psParseLevelInfo* info);
static psBool parseLine(psS32* level,   psArray* levelArray, char*  linePtr, psS32 lineCount,
                        char* fileName, psBool overwrite);
static psBool parseMetadataItem(char* keyName, psS32* level, psArray* levelArray, char* linePtr,
                                psS32 lineCount, char* fileName, psMetadataFlags flags);
static void parseLevelInfoFree(p_psParseLevelInfo* info)
{
    psFree(info->nonUniqueKeyArray);
    psFree(info->typeArray);
    psFree(info->templateArray);
    psFree(info->name);
    psFree(info->metadata);
}

// Determines if a line is blank (whitespace only) or a commentline. It returns true if so. The input string
// must be null terminated.
static psBool 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,
                         psS32 sLen,
                         psBool ignoreComment)
{
    char *ptrB = NULL;
    char *ptrE = NULL;
    char *cleaned = NULL;

    // Initialize begining of string pointer
    ptrB = inString;

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

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

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

    // Adds '\0' to end of string and +1 to sLen
    if(sLen < 0 ) {
        cleaned = NULL;
    } else {
        cleaned = psStringNCopy(ptrB, sLen);
    }

    return cleaned;
}

// Count repeat occurances of a single character within a line. The input string must be null terminated.
static psS32 repeatedChars(char *inString,
                           char ch)
{
    psS32 count = 0;

    while(*inString!='\0') {
        if(*inString == ch) {
            count++;
        }
        inString++;
    }

    return count;
}

// 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,
                      psS32 *status,
                      psBool ignoreComment)
{
    char *cleanToken = NULL;
    char *convertChar = NULL;
    psS32 sLen = 0;

    // Convert tab characters to white space
    while((convertChar=strchr(*inString,'\t')) != NULL ) {
        *convertChar = ' ';
    }

    // 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,ignoreComment);

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

    return cleanToken;
}

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

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

    return value;
}

/** Returns true or false. 'T', 't', '1', 'F', 'f', and '0' are acceptable, parsable variations. */
static psBool parseBool(char *inString,
                        psS32 *status)
{
    psBool value = false;


    if(*inString=='T' || *inString=='t' || *inString=='1') {
        value = true;
    } else if(*inString=='F' || *inString=='f' || *inString=='0') {
        value = false;
    } else {
        *status = 1;
    }

    return value;
}

/** Returns a psTime structure */
static psTime *parseTime(char *inString,
                         psTimeType tt,
                         psS32 *status)
{
    psTime *out;
    long sec = 0;
    unsigned int nsec = 0;
    char *end = NULL;
    out = psTimeAlloc(tt);

    sec = strtol(inString, &end, 10);
    out->sec = sec;
    while (!strncmp(end, " ", 1))
        end++;
    inString = end;
    nsec = (unsigned int)strtoul(inString, &end, 10);
    out->nsec = nsec;
    if (out->sec == 0 && out->nsec == 0)
        *status = 1;
    while (!strncmp(end, " ", 1))
        end++;
    inString = end;
    if ( !strncmp(inString, "false", 10) || !strncmp(inString, "f", 10) ||
            !strncmp(inString, "FALSE", 10) || !strncmp(inString, " F", 10) )
        out->leapsecond = false;
    else if ( !strncmp(inString, "true", 10) || !strncmp(inString, "t", 10) ||
              !strncmp(inString, "TRUE", 10) || !strncmp(inString, "T", 10) )
        out->leapsecond = true;
    else {
        *status = 1;
    }
    return out;
}

/** Returns parsed vector filled with with data. The input string must be null terminated. */
static psVector* parseVector(char *inString,
                             psElemType elemType,
                             psS32 *status)
{
    char*      end       = NULL;
    char*      saveValue = NULL;
    psS32      i         = 0;
    psS32      numValues = 0;
    double     value     = 0.0;
    psVector*  vec       = NULL;

    // Cycle through string and count entries
    saveValue = inString;
    while(*inString!='\0') {
        strtod(inString, &end);
        if(inString==end) {
            *status = 1;
            return NULL;
        }
        while(*end==' ' || *end==',') { // Commas or spaces may be used as delimiters for vector values
            end++;
        }
        inString=end;
        numValues++;
    }

    // Cycle through string and convert string values to values
    if(numValues) {
        inString = saveValue;
        end = NULL;
        vec = psVectorAlloc(numValues, elemType);

        while(*inString!='\0') {
            value = strtod(inString, &end);
            if(inString==end) {
                *status = 1;
                return vec;
            }
            switch(elemType) {
            case PS_TYPE_U8:
                vec->data.U8[i++] = (psU8)value;
                break;
            case PS_TYPE_U16:
                vec->data.U16[i++] = (psU16)value;
                break;
            case PS_TYPE_U32:
                vec->data.U32[i++] = (psU32)value;
                break;
            case PS_TYPE_U64:
                vec->data.U64[i++] = (psU64)value;
                break;
            case PS_TYPE_S8:
                vec->data.S8[i++] = (psS8)value;
                break;
            case PS_TYPE_S16:
                vec->data.S16[i++] = (psS16)value;
                break;
            case PS_TYPE_S32:
                vec->data.S32[i++] = (psS32)value;
                break;
            case PS_TYPE_S64:
                vec->data.S64[i++] = (psS64)value;
                break;
            case PS_TYPE_F32:
                vec->data.F32[i++] = (psF32)value;
                break;
            case PS_TYPE_F64:
                vec->data.F64[i++] = (psF64)value;
                break;
            default:
                *status = 1;
                psError(PS_ERR_BAD_PARAMETER_VALUE,true,
                        PS_ERRORTEXT_psMetadataIO_TYPE_INVALID,
                        elemType);
            }

            while(*end==' ' || *end==',') {
                end++;
            }
            inString=end;
        }
    }

    return vec;
}

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

p_psParseLevelInfo* p_psParseLevelInfoAlloc(void)
{

    p_psParseLevelInfo* info = NULL;

    // Allocate memory for parse level info
    info = (p_psParseLevelInfo*)psAlloc(sizeof(p_psParseLevelInfo));

    // If allocation successful then initialize members
    if(info != NULL) {

        info->nonUniqueKeyArray = psArrayAlloc(10);
        info->nonUniqueKeyArray->n = 0;

        info->typeArray = psArrayAlloc(10);
        info->typeArray->n = 0;

        info->templateArray = psArrayAlloc(10);
        info->templateArray->n = 0;

        info->metadata = NULL;
        info->name = NULL;

        // Set memory deallocator
        psMemSetDeallocator(info,(psFreeFunc)parseLevelInfoFree);
    }
    return info;
}

bool psMetadataItemPrint(FILE * fd,
                         const char *format,
                         const psMetadataItem* item)
{
    psDataType type;
    psBool success = true;

    PS_ASSERT_PTR_NON_NULL(fd, success);
    PS_ASSERT_PTR_NON_NULL(format, success);
    PS_ASSERT_PTR_NON_NULL(item, success);

    type = item->type;

    // determining the format type
    char* fType = strchr(format,'%');
    if (fType == NULL) {
        // well, the format contains no reference to the metadataItem's data:
        // that is truly trival to do!
        fprintf(fd,format);
        return success;
    }

    // skip over any format modifiers
    const char* formatEnd = format+strlen(format);
    while ( (fType < formatEnd) &&
        (strchr(" +-01234567890.$#, hlL",*(++fType)) != NULL) ) {}

    #define METADATAITEM_NUMERIC_CAST(FORMAT_TYPE) { \
        switch(type) { \
        case PS_DATA_BOOL: \
            fprintf(fd, format, (FORMAT_TYPE) item->data.B); \
            break; \
        case PS_DATA_S32: \
            fprintf(fd,format,(FORMAT_TYPE)  item->data.S32); \
            break; \
        case PS_DATA_F32: \
            fprintf(fd, format,(FORMAT_TYPE)  item->data.F32); \
            break; \
        case PS_DATA_F64: \
            fprintf(fd, format,(FORMAT_TYPE) item->data.F64); \
            break; \
        default: \
            psError(PS_ERR_BAD_PARAMETER_TYPE,true, \
                    PS_ERRORTEXT_psMetadata_METATYPE_INVALID, (int)type); \
            success = false; \
        } \
    }

    switch(*fType) {
    case 'd':
    case 'i':
    case 'c':
        METADATAITEM_NUMERIC_CAST(int)
        break;
    case 'o':
    case 'u':
    case 'x':
    case 'X':
        METADATAITEM_NUMERIC_CAST(unsigned int)
        break;
    case 'e':
    case 'E':
    case 'f':
    case 'F':
    case 'g':
    case 'G':
    case 'a':
    case 'A':
        METADATAITEM_NUMERIC_CAST(double)
        break;
    case 's':
        if (type == PS_DATA_STRING) {
            fprintf(fd,format,(char*)item->data.V);
        } else {
            psError(PS_ERR_BAD_PARAMETER_TYPE,true,
                    PS_ERRORTEXT_psMetadata_METATYPE_INVALID, (int)type);
            success = false;
        }
        break;
    case 'p':
        fprintf(fd,format,item->data.V);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_TYPE, true,
                PS_ERRORTEXT_psMetadata_FORMAT_INVALID, *fType);
        break;
    }

    return success;
}


psMetadata* psMetadataReadHeader(psMetadata* output,
                                 char *extName,
                                 psS32 extNum,
                                 char *fileName)
{
    psBool tempBool;
    psBool success;
    char keyType;
    char keyName[FITS_LINE_SIZE];
    char keyValue[FITS_LINE_SIZE];
    char keyComment[FITS_LINE_SIZE];
    char fitsErr[MAX_STRING_LENGTH];
    psS32 i;
    psS32 hduType = 0;
    psS32 status = 0;
    psS32 numKeys = 0;
    psS32 keyNum = 0;
    fitsfile *fd = NULL;

    PS_ASSERT_PTR_NON_NULL(fileName,NULL);

    fits_open_file(&fd, fileName, READONLY, &status);
    if(fd == NULL || status != 0) {
        FITS_ERROR("FITS error while opening file: %s %s", fileName);
        return NULL;
    }

    if (extName == NULL && extNum < 1) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psMetadataIO_EXTNUM_NOTPOSITIVE,
                extNum);
        return NULL;
    }

    // Allocate metadata if user didn't
    if (output == NULL) {
        output = psMetadataAlloc();
    }

    // Move to user designated HDU number or HDU name in FITS file. HDU numbers starts at one.
    if (extName != NULL) {
        if (fits_movnam_hdu(fd, ANY_HDU, extName, 0, &status) != 0) {
            FITS_ERROR("FITS error while locating header %s: %s", extName);
        }
    } else {
        if (fits_movabs_hdu(fd, extNum, &hduType, &status) != 0) {
            FITS_ERROR("FITS error while locating header %d: %s", extNum);
        }
    }

    // Get number of key names
    if (fits_get_hdrpos(fd, &numKeys, &keyNum, &status) != 0) {
        FITS_ERROR("FITS error while reading key %d: %s", keyNum);
    }

    // Get each key name. Keywords start at one.
    for (i = 1; i <= numKeys; i++) {
        if (fits_read_keyn(fd, i, keyName, keyValue, keyComment, &status) != 0) {
            FITS_ERROR("FITS error while reading key %d: %s", keyNum);
        }
        if (fits_get_keytype(keyValue, &keyType, &status) != 0) {
            fits_get_errstatus(status, fitsErr);
            if (status != VALUE_UNDEFINED) {
                FITS_ERROR("FITS error while determining key %d type: %s", keyNum);
            } else {
                // Some keywords are still valid if they don't have a type (like COMMENTS and HISTORY)
                keyType = 'C';
                status = 0;
            }
        }

        switch (keyType) {
        case 'I':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_DATA_S32 | PS_META_DUPLICATE_OK,
                                    keyComment, atoi(keyValue));
            break;
        case 'F':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_DATA_F64 | PS_META_DUPLICATE_OK,
                                    keyComment, atof(keyValue));
            break;
        case 'C':
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_DATA_STRING | PS_META_DUPLICATE_OK,
                                    keyComment, keyValue);
            break;
        case 'L':
            tempBool = (keyValue[0] == 'T') ? 1 : 0;
            success = psMetadataAdd(output, PS_LIST_TAIL, keyName,
                                    PS_DATA_BOOL | PS_META_DUPLICATE_OK,
                                    keyComment, tempBool);
            break;
        case 'U':
        case 'X':
        default:
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FITS_METATYPE_INVALID, keyType);
            return output;
        }

        if (!success) {
            psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadataIO_ADD_FAILED, keyName);
            return output;
        }
    }

    return output;
}

static psMetadata* getMetadataType(char* linePtr)
{
    psMetadata*     metadataTemplate = NULL;
    psS32           status           = 0;
    char*           token            = NULL;
    psMetadataItem* tempItem         = NULL;

    // Loop through line and generate metadata items for each token found
    while((token = getToken(&linePtr," ",&status,false)) != NULL ) {

        // If not allocated then allocate new metadata
        if(metadataTemplate == NULL) {
            metadataTemplate = psMetadataAlloc();
        }

        // Look for comment indicator #
        if(strstr(token,"#") != 0) {
            psFree(token);
            break;
        }

        // Create metadata item to represent token read
        tempItem = psMetadataItemAllocStr(token,"","");
        if(tempItem == NULL) {
            psFree(metadataTemplate);
            psFree(token);
            metadataTemplate = NULL;
            break;
        }

        // Add item to template
        if(!psMetadataAddItem(metadataTemplate, tempItem, PS_LIST_TAIL, PS_META_DEFAULT)) {
            psFree(metadataTemplate);
            psFree(tempItem);
            psFree(token);
            metadataTemplate = NULL;
            break;
        }
        psFree(tempItem);
        psFree(token);
    }

    return metadataTemplate;
}

static psMetadata* setMetadataItem(psMetadata* template,
                                   char* linePtr)
{
    psMetadata*      md           = NULL;
    psS32            items        = 0;
    char*            token        = NULL;
    psS32            status       = 0;
    psMetadataItem*  mdItem       = NULL;
    psMetadataItem*  templateItem = NULL;
    psListIterator*  iter         = NULL;

    // Determine the number of items in template
    items = template->list->n;
    if(items > 0 ) {

        // Allocate metadata
        md = psMetadataAlloc()
             ;

        // Point to first item in template
        iter = psListIteratorAlloc(template->
                                   list,PS_LIST_HEAD,true);

        // For each item in template parse line string for values
        for(psS32 i = 0;
                i < items;
                i++) {

            // Get template item
            templateItem = psListGetAndIncrement(iter)
                           ;
            if(templateItem == NULL) {
                psFree(md);
                md = NULL;
                break;
            }

            // Get the next token on the line
            token = getToken(&linePtr," ",&status,false);
            if(token != NULL) {
                // Allocate metadata item
                mdItem = psMetadataItemAllocStr(templateItem->name,templateItem->comment,token);
                if(mdItem == NULL) {
                    psFree(md);
                    md = NULL;
                    psFree(token);
                    break;
                }
                // Add item to metadata
                if(!psMetadataAddItem(md, mdItem, PS_LIST_TAIL, PS_META_DEFAULT)) {
                    psFree(md);
                    md = NULL;
                    psFree(mdItem);
                    psFree(token);
                    break;
                }
                psFree(mdItem);
            } else {
                // Missing items
                psFree(md);
                md = NULL;
                break;
            }

            psFree(token);
        }
        psFree(iter);
    }
    return md;
}

psBool parseMetadataItem(char* keyName,
                         psS32* level,
                         psArray* levelArray,
                         char* linePtr,
                         psS32 lineCount,
                         char* fileName,
                         psMetadataFlags flags)
{
    psBool               returnValue   = true;
    psBool               addStatus     = false;
    psDataType           mdType        = PS_DATA_UNKNOWN;
    psElemType           vectorType    = PS_TYPE_S8;
    char*                strType       = NULL;
    char*                strValue      = NULL;
    char*                strComment    = NULL;
    psS32                status        = 0;
    psMetadata*          md            = NULL;
    psF64                tempDbl       = 0.0;
    psBool               tempBool      = false;
    psS32                tempInt       = 0;
    psVector*            tempVec       = NULL;
    char*                tempStr       = NULL;
    psArray*             nonUniqueKeys = NULL;
    psBool               typeFound     = false;
    psArray*             typeArray     = NULL;
    psArray*             templateArray = NULL;
    psMetadata*          tempMeta      = NULL;
    p_psParseLevelInfo*  nextLevelInfo = NULL;
    psTime *mTime;
    psTimeType timeType;

    // Get the metadata item type
    strType = getToken(&linePtr, " ", &status,true);

    // Check for no type
    if(strType==NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "type",lineCount,
                fileName);
        returnValue = false;
    } else {

        // Set metadata type based on type token

        // Check if the keyName specifies a vector and if so use strType token to find vector type
        if(*keyName == '@') {
            mdType = PS_DATA_VECTOR;
            // Get the type of vector
            if(!strncmp(strType, "U8", 2)) {
                vectorType = PS_TYPE_U8;
            } else if (!strncmp(strType,"U16",3)) {
                vectorType = PS_TYPE_U16;
            } else if (!strncmp(strType,"U32",3)) {
                vectorType = PS_TYPE_U32;
            } else if (!strncmp(strType,"U64",3)) {
                vectorType = PS_TYPE_U64;
            } else if (!strncmp(strType,"S8",2)) {
                vectorType = PS_TYPE_S8;
            } else if (!strncmp(strType,"S16",3)) {
                vectorType = PS_TYPE_S16;
            } else if (!strncmp(strType,"S32",3)) {
                vectorType = PS_TYPE_S32;
            } else if (!strncmp(strType,"S64",3)) {
                vectorType = PS_TYPE_S64;
            } else if (!strncmp(strType,"F32",3)) {
                vectorType = PS_TYPE_F32;
            } else if (!strncmp(strType,"F64",3)) {
                vectorType = PS_TYPE_F64;
            } else {
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, "", keyName,
                        strType, lineCount, fileName);
                psFree(strType);
                return false;
            }
        } else if(!strncmp(strType, "STR", 3)) {
            mdType = PS_DATA_STRING;
        } else if(!strncmp(strType, "BOOL", 4)) {
            mdType = PS_DATA_BOOL;
        } else if(!strncmp(strType, "S32", 3)) {
            mdType = PS_DATA_S32;
        } else if(!strncmp(strType, "F32", 3)) {
            mdType = PS_DATA_F32;
        } else if(!strncmp(strType, "F64", 3)) {
            mdType = PS_DATA_F64;
        } else if(!strncmp(strType, "MULTI", 5)) {
            mdType = PS_DATA_METADATA_MULTI;
        } else if(!strncmp(strType, "METADATA", 8)) {
            mdType = PS_DATA_METADATA;
        } else if( !strncmp(strType, "PS_TIME_UTC", 12) || !strncmp(strType, "PS_TIME_TAI", 12)
                   || !strncmp(strType, "PS_TIME_UT1", 12) || !strncmp(strType, "PS_TIME_TT", 12)) {
            mdType = PS_DATA_TIME;
        } else {
            // Search through user types
            typeArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->typeArray;
            templateArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->templateArray;
            for(psS32 k = 0; k < typeArray->n; k++) {
                if(strcmp(strType,(char*)typeArray->data[k]) == 0) {
                    tempMeta = setMetadataItem((psMetadata*)templateArray->data[k],linePtr);
                    if(tempMeta != NULL) {
                        // Add metadata item
                        md = ((p_psParseLevelInfo*)(levelArray->data[*level]))->metadata;
                        addStatus = psMetadataAdd(md,PS_LIST_TAIL,keyName,PS_DATA_METADATA | flags,"",tempMeta);
                        // Check for add failure
                        if (! addStatus) {
                            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM,
                                    keyName, lineCount, fileName);
                            psFree(strType);
                            psFree(tempMeta);
                            return false;
                        }
                        psFree(tempMeta);
                    } else {
                        // Metadata type read error
                        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL,
                                keyName,lineCount,fileName);
                        psFree(strType);
                        return false;
                    }
                    typeFound = true;
                    break;
                }
            }
            if(!typeFound) {
                psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                        strType, lineCount,fileName);
                psFree(strType);
                return false;
            } else {
                psFree(strType);
                return true;
            }
        }
    }

    // If type is not MULTI or META then get the value and comment
    if((mdType != PS_DATA_METADATA_MULTI) && (mdType != PS_DATA_METADATA)) {
        // Get the metadata item value if there is one.
        status = 0;
        strValue = getToken(&linePtr, "#", &status,true);
        if(status) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "value", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            return false;
        }
        if(strValue==NULL) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "value", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            return false;
        }
        // Not all lines will have comments, so NULL is ok.
        status = 0;
        strComment = getToken(&linePtr,"~", &status,true);
        if(status) {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "comment", lineCount,
                    fileName);
            psFree(strType);
            psFree(strValue);
            psFree(strComment);
        }
    }

    // Need to add item to metadata so get pointer to metadata
    status = 0;
    md = ((p_psParseLevelInfo*)(levelArray->data[*level]))->metadata;
    nonUniqueKeys = ((p_psParseLevelInfo*)(levelArray->data[*level]))->nonUniqueKeyArray;
    switch(mdType) {
    case PS_DATA_STRING:
        addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                  mdType | flags,
                                  strComment, strValue);
        break;
    case PS_DATA_BOOL:
        tempBool = parseBool(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempBool);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        break;
    case PS_DATA_F32:
    case PS_DATA_F64:
        tempDbl = parseValue(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempDbl);
        } else {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName, strType, lineCount,
                    fileName);
            returnValue = false;
        }
        break;
    case PS_DATA_S32:
        tempInt = (psS32)parseValue(strValue, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, tempInt);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        break;
    case PS_DATA_TIME:
        if( !strncmp(strType, "PS_TIME_UTC", 12) )
            timeType = PS_TIME_UTC;
        else if( !strncmp(strType, "PS_TIME_TAI", 12) )
            timeType = PS_TIME_TAI;
        else if( !strncmp(strType, "PS_TIME_UT1", 12) )
            timeType = PS_TIME_UT1;
        else if( !strncmp(strType, "PS_TIME_TT", 12) )
            timeType = PS_TIME_TT;
        mTime = parseTime(strValue, timeType, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName,
                                      mdType | flags,
                                      strComment, mTime);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        psFree(mTime);
        break;
    case PS_DATA_VECTOR:
        tempVec = parseVector(strValue, vectorType, &status);
        if(!status) {
            addStatus = psMetadataAdd(md, PS_LIST_TAIL, keyName+1,
                                      mdType | flags,
                                      strComment, tempVec);
        } else {
            psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_PARSE_FAILED, strValue, keyName,
                    strType, lineCount, fileName);
            returnValue = false;
        }
        psFree(tempVec);
        break;
    case PS_DATA_METADATA_MULTI:
        // Add key to non-unique array of keys
        // Check for duplicate MULTI lines
        addStatus = true;
        for(psS32 k=0; k < nonUniqueKeys->n; k++) {
            if(strcmp(keyName,(char*)nonUniqueKeys->data[k]) == 0) {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_DUPLICATE_MULTI,
                        lineCount,fileName);
                psFree(strType);
                return false;
            }
        }
        tempStr = psStringCopy(keyName);
        nonUniqueKeys = psArrayAdd(nonUniqueKeys,0,tempStr);
        addStatus = true;
        psFree(tempStr);
        break;
    case PS_DATA_METADATA:
        // Create next level info
        nextLevelInfo = p_psParseLevelInfoAlloc();
        // Create new metadata
        nextLevelInfo->metadata = psMetadataAlloc();
        // Save name of metadata
        nextLevelInfo->name = psStringCopy(keyName);
        // Add next level to levelArray
        levelArray = psArrayAdd(levelArray,0,nextLevelInfo);
        psFree(nextLevelInfo);
        // Increment level counter
        (*level)++;
        addStatus = true;
        break;
    default:
        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                lineCount,fileName);
        break;
    }

    // Check if the add status was successful
    if (! addStatus) {
        //        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_OVERWRITE_ITEM, keyName, lineCount,
        //                fileName);
        returnValue = false;
    }

    psFree(strComment);
    psFree(strValue);
    psFree(strType);

    return returnValue;
}

psBool parseLine(psS32* level,
                 psArray* levelArray,
                 char* linePtr,
                 psS32 lineCount,
                 char* fileName,
                 psBool overwrite)
{
    psBool              returnValue    = true;
    psMetadataFlags     flags          = PS_META_DEFAULT;
    char*               keyName        = NULL;
    psS32               status         = 0;
    psS32               limit          = 0;
    psMetadata*         tempTemplate   = NULL;
    char*               strType        = NULL;
    psArray*            typeArray      = NULL;
    psArray*            templateArray  = NULL;
    p_psParseLevelInfo* upperLevelInfo = NULL;
    p_psParseLevelInfo* lowerLevelInfo = NULL;
    psBool              addStatus      = 0;

    // Set flags if overwrite specified
    if(overwrite) {
        flags = PS_META_REPLACE;
    }

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

        // Check for more than one '@' in a line
        if(repeatedChars(linePtr, '@') > 1) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_FILE_MULTIPLE_CHAR, '@', lineCount, fileName);
            return false;
        }

        // Get metadata item name
        keyName = getToken(&linePtr, " ", &status,true);
        if(status) {
            psError(PS_ERR_IO, true,
                    PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL, "keyName", lineCount, fileName);
            psFree(keyName);
            return false;
        }

        // Check for special keyName values "TYPE", "END"
        if(strcmp(keyName,"TYPE") == 0 ) {
            // Get the type name
            strType = getToken(&linePtr," ",&status,true);
            if(strType == NULL) {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_ELEMENT_NULL,"type",lineCount,
                        fileName);
                psFree(keyName);
                return false;
            }
            tempTemplate = getMetadataType(linePtr);
            // Check if type was parsed succesfully
            if(tempTemplate != NULL) {
                // Access type array
                typeArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->typeArray;
                // Check if type already exists in array
                for(psS32 k=0; k < typeArray->n; k++) {
                    // Compare type name with the list of current types
                    if(strcmp(strType,(char*)typeArray->data[k]) == 0) {
                        psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_TYPE_DUPLICATE,
                                strType,lineCount,fileName);
                        psFree(tempTemplate);
                        psFree(keyName);
                        psFree(strType);
                        return false;
                    }
                }
                // Add key name to array of type
                typeArray = psArrayAdd(typeArray,1,strType);
                // Add template to array of templates
                templateArray = ((p_psParseLevelInfo*)(levelArray->data[*level]))->templateArray;
                templateArray = psArrayAdd(templateArray,1,tempTemplate);
                psFree(tempTemplate);
            } else {
                psError(PS_ERR_IO,true,PS_ERRORTEXT_psMetadataIO_FILE_TYPE_INVALID,
                        strType,lineCount,fileName);
                psFree(keyName);
                psFree(strType);
                return false;
            }
            psFree(strType);
        } else if (strcmp(keyName,"END") == 0 ) {
            if(*level > 0) {
                upperLevelInfo = ((p_psParseLevelInfo*)(levelArray->data[*level-1]));
                lowerLevelInfo = ((p_psParseLevelInfo*)(levelArray->data[*level]));
                // Check name in nonunique list
                for(psS32 k=0; k < upperLevelInfo->nonUniqueKeyArray->n; k++) {
                    if(strcmp(upperLevelInfo->nonUniqueKeyArray->data[k],lowerLevelInfo->name) == 0) {
                        flags = PS_META_DUPLICATE_OK;
                        break;
                    }
                }
                // Add metadata to upper level metadata
                addStatus = psMetadataAdd(upperLevelInfo->metadata,
                                          PS_LIST_TAIL,lowerLevelInfo->name,
                                          PS_DATA_METADATA | flags,
                                          "",
                                          lowerLevelInfo->metadata);
                if(!addStatus) {
                    psFree(keyName);
                    return false;
                } else {
                    // Remove lower info level
                    if(!psArrayRemove(levelArray,levelArray->data[*level])) {
                        psFree(keyName);
                        return false;
                    }
                    (*level)--;
                }
            } else {
                psFree(keyName);
                return false;
            }
        } else {
            // Check if key name present in array of non-unique key names
            limit = ((p_psParseLevelInfo*)(levelArray->data[*level]))->nonUniqueKeyArray->n;
            for(psS32 k=0; k < limit; k++) {
                char* name = (char*)((p_psParseLevelInfo*)
                                     (levelArray->data[*level]))->nonUniqueKeyArray->data[k];
                if(strcmp(name,keyName) == 0) {
                    flags = PS_META_DUPLICATE_OK;
                }
            }
            // Parse metadataItem
            if(!parseMetadataItem(keyName,level, levelArray, linePtr, lineCount, fileName, flags)) {
                psFree(keyName);
                return false;
            }
        }
        psFree(keyName);
    }
    return returnValue;
}

psMetadata* psMetadataConfigParse(psMetadata* md,
                                  unsigned int *nFail,
                                  const char *filename,
                                  bool overwrite)
{
    FILE*               fp                   = NULL;
    char*               line                 = NULL;
    char*               linePtr              = NULL;
    psArray*            parseLevelInfoArray  = NULL;
    psS32               lineCount            = 0;
    psS32               nestingLevel         = 0;
    p_psParseLevelInfo* topLevelInfo         = NULL;

    // Check for NULL file name
    PS_ASSERT_PTR_NON_NULL(filename,NULL);

    // Check for NULL nFail
    PS_ASSERT_PTR_NON_NULL(nFail,NULL);

    // Attempt to open specified file
    if((fp=fopen(filename, "r")) == NULL) {
        psError(PS_ERR_IO, true, PS_ERRORTEXT_psMetadataIO_FILE_OPEN_FAILED, filename);
        return NULL;
    }

    // Allocate metadata if necessary
    if (md == NULL) {
        md = psMetadataAlloc();
    }

    // Allocate array to store parse level information
    parseLevelInfoArray = psArrayAlloc(10);
    parseLevelInfoArray->n = 0;

    // Set parse level info for the top level
    topLevelInfo = p_psParseLevelInfoAlloc();
    topLevelInfo->metadata = psMemIncrRefCounter(md);
    parseLevelInfoArray = psArrayAdd(parseLevelInfoArray,1,topLevelInfo);
    psFree(topLevelInfo);

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

    // While loop to parse the file
    while(fgets(line, MAX_STRING_LENGTH, fp) != NULL) {

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

        if(!parseLine(&nestingLevel,parseLevelInfoArray,linePtr,lineCount,(char*)filename,overwrite)) {
            (*nFail)++;
        }
    }

    // Free parse array and line buffer
    psFree(parseLevelInfoArray);
    psFree(line);

    return md;
}

psString psMetadataConfigFormat(psMetadata *md)
{
    PS_ASSERT_PTR_NON_NULL(md, NULL);

    psString newString = NULL;
    psString newStr = NULL;
    char mdString[16000];
    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc(md, PS_LIST_HEAD, NULL);
    psDataType type = PS_DATA_UNKNOWN;
    //    int size = 0;
    int i = 0;
    char content[MAXSTR];
    char buffer[MAXSTR];
    char *buffer2;
    strcpy(content, "\0");
    strcpy(mdString, "\0");

    while ( (item = psMetadataGetAndIncrement(iter)) ) {
        type = item->type;
        if ( type == PS_DATA_STRING) {
            type = PS_DATA_STRING;
        }
        if ( type == PS_DATA_VECTOR) {
            type = PS_DATA_VECTOR;
        }
        if ( type == PS_DATA_TIME) {
            type = PS_DATA_TIME;
        }
        if ( item->type == PS_DATA_METADATA) {
            type = PS_DATA_METADATA;
        }
        if (item == NULL) {
            type = PS_DATA_UNKNOWN;
        }

        switch (type) {
        case PS_DATA_BOOL:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "%s BOOL  ", item->name);
            strncat(mdString, content, MAXSTR);
            if ( item->data.B )
                strncat(mdString, "TRUE", MAXSTR);
            else
                strncat(mdString, "FALSE", MAXSTR);
            if ( item->comment != NULL ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_S32:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "%s S32  ", item->name);
            strncat(mdString, content, MAXSTR);
            snprintf(content, MAXSTR, "%d ", item->data.S32);
            strncat(mdString, content, MAXSTR);
            if ( strncmp(item->comment,"",2) ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_F32:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "%s F32  ", item->name);
            strncat(mdString, content, MAXSTR);
            snprintf(content, MAXSTR, "%f ", item->data.F32);
            strncat(mdString, content, MAXSTR);
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_F64:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "%s F64  ", item->name);
            strncat(mdString, content, MAXSTR);
            snprintf(content, MAXSTR, "%f ", item->data.F64);
            strncat(mdString, content, MAXSTR);
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                //            if ( !strncmp((char*)item->comment, "", 2) ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_STRING:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "%s STR  ", item->name);
            strncat(mdString, content, MAXSTR);
            //            size = strlen(((char *)(item->data.V)));
            snprintf(content, MAXSTR, "%s ", ((char *)(item->data.V)));
            strncat(mdString, content, MAXSTR);
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_METADATA:
            snprintf(content, MAXSTR, "\n%s  METADATA  ", item->name);
            strncat(mdString, content, MAXSTR);

            newStr = psMetadataConfigFormat(item->data.md);
            strncpy(buffer, (char *)newStr, MAXSTR);
            buffer2 = strtok(buffer, "\n");
            while( buffer2 != NULL ) {
                snprintf(content, MAXSTR, "\n   %s", buffer2);
                strncat(mdString, content, MAXSTR);
                buffer2 = strtok(NULL, "\n");
            }
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                snprintf(content, MAXSTR, "\nEND   #%s\n\n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\nEND\n\n");
                strncat(mdString, content, MAXSTR);
            }
            psFree(newStr);
            break;
        case PS_DATA_TIME:
            snprintf(content, MAXSTR, "%s ", item->name);
            if ( ((psTime*)(item->data.V))->type == PS_TIME_UTC )
                strncat(content, "PS_TIME_UTC  ", MAXSTR);
            else if ( ((psTime*)(item->data.V))->type == PS_TIME_TAI )
                strncat(content, "PS_TIME_TAI  ", MAXSTR);
            else if ( ((psTime*)(item->data.V))->type == PS_TIME_UT1 )
                strncat(content, "PS_TIME_UT1  ", MAXSTR);
            else if ( ((psTime*)(item->data.V))->type == PS_TIME_TT )
                strncat(content, "PS_TIME_TT  ", MAXSTR);
            else {
                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                        PS_ERRORTEXT_psMetadata_METATYPE_INVALID, type);
                psFree(iter);
                return NULL;
            }
            strncat(mdString, content, MAXSTR);

            snprintf(content, MAXSTR, "%ld, ", (long)((psTime*)(item->data.V))->sec);
            strncat(mdString, content, MAXSTR);
            snprintf(content, MAXSTR, "%u, ", ((psTime*)(item->data.V))->nsec);
            strncat(mdString, content, MAXSTR);
            if ( ((psTime*)(item->data.V))->leapsecond )
                strncat(mdString, "T ", MAXSTR);
            else
                strncat(mdString, "F ", MAXSTR);
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_VECTOR:
            //            size = strlen(item->name);
            snprintf(content, MAXSTR, "@%s ", item->name);
            strncat(mdString, content, MAXSTR);
            type = ((psVector*)(item->data.V))->type.type;
            switch (type) {
            case PS_DATA_S32:
                strncat(mdString, "S32 ", MAXSTR);
                for (i = 0; ((psVector*)(item->data.V))->data.S32[i] != 0; i++) {
                    snprintf(content, MAXSTR, "%d",
                             ((psVector*)(item->data.V))->data.S32[i]);
                    if ( ((psVector*)(item->data.V))->data.S32[i+1] != 0 ) {
                        strncat(content, ", ", MAXSTR);
                    }
                    strncat(mdString, content, MAXSTR);
                }
                break;
            case PS_DATA_F32:
                strncat(mdString, "F32 ", MAXSTR);
                for (i = 0; ((psVector*)(item->data.V))->data.F32[i] != 0; i++) {
                    snprintf(content, MAXSTR, "%f",
                             ((psVector*)(item->data.V))->data.F32[i]);
                    if ( ((psVector*)(item->data.V))->data.F32[i+1] != 0 ) {
                        strncat(content, ", ", MAXSTR);
                    }
                    strncat(mdString, content, MAXSTR);
                }
                break;
            case PS_DATA_F64:
                strncat(mdString, "F64 ", MAXSTR);
                for (i = 0; ((psVector*)(item->data.V))->data.F64[i] != 0; i++) {
                    snprintf(content, MAXSTR, "%lf",
                             ((psVector*)(item->data.V))->data.F64[i]);
                    if ( ((psVector*)(item->data.V))->data.F64[i+1] != 0 ) {
                        strncat(content, ", ", MAXSTR);
                    }
                    strncat(mdString, content, MAXSTR);
                }
                break;
            case PS_DATA_UNKNOWN:
            default:
                psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                        PS_ERRORTEXT_psMetadata_METATYPE_INVALID, type);
                psFree(iter);
                return NULL;
            }
            if ( strncmp(item->comment,"",2) ) {
                //            if ( item->comment != NULL ) {
                //                size = strlen(item->comment);
                snprintf(content, MAXSTR, " #%s \n", item->comment);
                strncat(mdString, content, MAXSTR);
            } else {
                snprintf(content, MAXSTR, "\n");
                strncat(mdString, content, MAXSTR);
            }
            break;
        case PS_DATA_UNKNOWN:
        default:
            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                    PS_ERRORTEXT_psMetadata_METATYPE_INVALID, type);
            psFree(iter);
            return NULL;
        }
    }
    newString = psStringNCopy(mdString, 16000);
    psFree(iter);
    return newString;
}

bool psMetadataConfigWrite(psMetadata *md,
                           const char *filename)
{
    psString fileString = NULL;
    FILE *file;
    file = fopen(filename, "w");
    fileString = psMetadataConfigFormat(md);
    fprintf(file, "%s", fileString);
    fclose(file);
    psFree(fileString);
    return true;
}

