
/** @file  psMetadata.c
*
*
*  @brief Contains metadata structures, enumerations and functions prototypes.
*
*  This file defines metadata item, metadata type, metadata flags, metadata containers, and function
*  prototypes necessary creating psLib metadata APIs
*
*  @ingroup Metadata
*
*  @author Ross Harman, MHPCC
*
*  @version $Revision: 1.49 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-01-17 20:58:20 $
*
*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
*/

/******************************************************************************/
/*  INCLUDE FILES                                                             */
/******************************************************************************/
#include<stdio.h>
#include<stdarg.h>
#include<string.h>

#include "fitsio.h"
#include "psType.h"
#include "psMemory.h"
#include "psError.h"
#include "psAbort.h"
#include "psList.h"
#include "psHash.h"
#include "psVector.h"
#include "psMetadata.h"
#include "psLookupTable.h"
#include "psString.h"
#include "psAstronomyErrors.h"
#include "psConstants.h"

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

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

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

// None

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

// None

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

static psS32 metadataId = 0;

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

static void metadataItemFree(psMetadataItem* metadataItem)
{
    psMetadataType type;

    type = metadataItem->type;

    if (metadataItem == NULL) {
        return;
    }

    psFree(metadataItem->name);
    psFree(metadataItem->comment);
    if (type != PS_META_PRIMITIVE) {
        psFree(metadataItem->data.V);
    }
}

static void metadataFree(psMetadata* metadata)
{
    if (metadata == NULL) {
        return;
    }
    psFree(metadata->list);
    psFree(metadata->table);
}

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

psMetadataItem* psMetadataItemAlloc(const char *name, psElemType pType, psMetadataType type,
                                    const char *comment, ...)
{
    va_list argPtr;
    psMetadataItem* metadataItem = NULL;

    // Get the variable list parameters to pass to allocation function
    va_start(argPtr, comment);

    // Call metadata item allocation
    metadataItem = psMetadataItemAllocV(name, pType, type, comment, argPtr);

    // Clean up stack after variable arguement has been used
    va_end(argPtr);

    return metadataItem;
}

psMetadataItem* psMetadataItemAllocV(const char *name, psElemType pType, psMetadataType type,
                                     const char *comment, va_list argPtr)
{
    psMetadataItem* metadataItem = NULL;


    PS_PTR_CHECK_NULL(name,NULL);

    // Allocate metadata item
    metadataItem = (psMetadataItem*) psAlloc(sizeof(psMetadataItem));
    metadataItem->data.V = NULL;

    // Set deallocator
    p_psMemSetDeallocator(metadataItem, (psFreeFcn) metadataItemFree);

    // Allocate and set metadata item comment
    metadataItem->comment = (char *)psAlloc(sizeof(char) * MAX_STRING_LENGTH);
    if (comment == NULL) {
        // Per SDRS, null isn't allowed, must use "" instead
        strncpy(metadataItem->comment, "", MAX_STRING_LENGTH);
    } else {
        strncpy(metadataItem->comment, comment, MAX_STRING_LENGTH);
    }

    // Set metadata item unique id
    *(psS32 *)(&metadataItem->id) = ++metadataId;

    // Set metadata item type
    metadataItem->type = type;
    metadataItem->pType = pType;

    // Allocate and set metadata item name
    metadataItem->name = (char *)psAlloc(sizeof(char) * MAX_STRING_LENGTH);
    vsprintf(metadataItem->name, name, argPtr);

    // Set metadata item value
    switch(type) {
    case PS_META_PRIMITIVE:
        switch(pType) {
        case PS_TYPE_BOOL:
            metadataItem->data.B = (psBool)va_arg(argPtr, psS32);
            break;
        case PS_TYPE_S32:
            metadataItem->data.S32 = (psS32)va_arg(argPtr, psS32);
            break;
        case PS_TYPE_F32:
            metadataItem->data.F32 = (psF32)va_arg(argPtr, psF64);
            break;
        case PS_TYPE_F64:
            metadataItem->data.F64 = (psF64)va_arg(argPtr, psF64);
            break;
        default:
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_METATYPE_INVALID, type);
            psFree(metadataItem);
            metadataItem = NULL;
        }
        break;
    case PS_META_STR:
        // Perform copy of input strings
        metadataItem->data.V = psStringNCopy(va_arg(argPtr, char *), MAX_STRING_LENGTH);
        break;
    case PS_META_LIST:
    case PS_META_VEC:
    case PS_META_HASH:
    case PS_META_LOOKUPTABLE:
    case PS_META_JPEG:
    case PS_META_PNG:
    case PS_META_ASTROM:
    case PS_META_UNKNOWN:
        // Copy of input data not performed due to variability of data types
        metadataItem->data.V = va_arg(argPtr, psPtr);
        break;
    default:
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_METATYPE_INVALID, type);
        psFree(metadataItem);
        metadataItem = NULL;
    }

    return metadataItem;
}

psMetadata* psMetadataAlloc(void)
{
    psList* list = NULL;
    psHash* table = NULL;
    psMetadata* metadata = NULL;

    // Allocate metadata
    metadata = (psMetadata*) psAlloc(sizeof(psMetadata));
    // Set deallocator
    p_psMemSetDeallocator(metadata, (psFreeFcn) metadataFree);

    // Allocate metadata's internal containers
    list = (psList*) psListAlloc(NULL);
    table = (psHash*) psHashAlloc(10);

    metadata->list = list;
    metadata->table = table;

    return metadata;
}

psBool psMetadataAddItem(psMetadata *md, psMetadataItem *metadataItem, psS32 location)
{
    char * key = NULL;
    psHash *mdTable = NULL;
    psList *mdList = NULL;
    psMetadataItem *existingEntry = NULL;
    psMetadataItem *newFolderEntry = NULL;
    psMetadataType newType;

    PS_PTR_CHECK_NULL(md,NULL);
    PS_PTR_CHECK_NULL(md->table,NULL);
    PS_PTR_CHECK_NULL(md->list,NULL);
    PS_PTR_CHECK_NULL(metadataItem,NULL);
    PS_PTR_CHECK_NULL(metadataItem->name,NULL);

    newType = metadataItem->type;
    mdTable = md->table;
    mdList = md->list;
    key = metadataItem->name;

    // See if key is already in table
    existingEntry = (psMetadataItem*)psHashLookup(mdTable, key);
    if(existingEntry != NULL) {

        if(existingEntry->type == PS_META_LIST) {

            if(existingEntry->data.list == NULL) {
                existingEntry->data.list = psListAlloc(NULL);
            }

            // Add leaf node to existing folder node
            if(!psListAdd(existingEntry->data.list, PS_LIST_TAIL, metadataItem)) {
                psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED, metadataItem->name);
                return false;
            }
        } else if(existingEntry->type != PS_META_LIST && newType!= PS_META_LIST) {

            // Leaf node replaces another leaf. Make new folder node and add leaves.
            newFolderEntry = psMetadataItemAlloc(key, PS_TYPE_PTR, PS_META_LIST, NULL, NULL);
            newFolderEntry->data.list = psListAlloc(NULL);

            if(!psListAdd(newFolderEntry->data.list, PS_LIST_TAIL, existingEntry)) {
                psError(PS_ERR_UNKNOWN,false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psListAdd(newFolderEntry->data.list, PS_LIST_TAIL, metadataItem)) {
                psError(PS_ERR_UNKNOWN,false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psHashRemove(mdTable, key)) {
                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED,key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psHashAdd(mdTable, key, newFolderEntry)) {
                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_TABLE_FAILED,key);
                psFree(newFolderEntry);
                return false;
            }

            // Remove local reference to new folder node
            psMemDecrRefCounter(newFolderEntry);
        } else {

            // Folder node replaces leaf or folder node - Put old node into new folder and remove from table
            if(metadataItem->data.list == NULL) {
                metadataItem->data.list = psListAlloc(NULL);
            }

            if(!psListAdd(metadataItem->data.list, PS_LIST_TAIL, existingEntry)) {
                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
                return false;
            }

            if(!psHashRemove(mdTable, key)) {
                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED,key);
                return false;
            }

            if(!psHashAdd(mdTable, key, metadataItem)) {
                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_TABLE_FAILED,key);
                return false;
            }
        }
    } else {

        // Node doesn't exist - Add new metadata item to metadata collection's hash
        if(!psHashAdd(mdTable, key, metadataItem)) {
            psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_TABLE_FAILED,key);
            return false;
        }
    }

    // Add items to metadata collection's list, even if they have the same metadata item names. Folder nodes
    // (PS_META_LIST metadata items) are not added, since the metadata list is flat.
    if(metadataItem->type != PS_META_LIST) {
        if(!psListAdd(mdList, location, metadataItem)) {
            psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_COLLECTION_FAILED,key);
            return false;
        }
    }

    return true;
}

psBool psMetadataAdd(psMetadata *md, psS32 where, const char *name, psElemType pType,
                     psMetadataType type, const char *comment, ...)
{
    va_list argPtr;
    psMetadataItem* metadataItem = NULL;

    va_start(argPtr, comment);
    metadataItem = psMetadataItemAllocV(name, pType, type, comment, argPtr);
    va_end(argPtr);

    if (!psMetadataAddItem(md, metadataItem, where)) {
        psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_FAILED);
        psFree(metadataItem);
        return false;
    }
    // Decrement reference count, since the metadata item is now in metadata collection and no longer needed
    psMemDecrRefCounter(metadataItem);

    return true;
}

psBool psMetadataRemove(psMetadata *md, psS32 where, const char *key)
{
    psList* mdList = NULL;
    psHash* mdTable = NULL;
    psMetadataItem* entry = NULL;


    PS_PTR_CHECK_NULL(md,NULL);
    PS_PTR_CHECK_NULL(md->table,NULL);
    PS_PTR_CHECK_NULL(md->list,NULL);

    mdList = md->list;
    mdTable = md->table;

    // Select removal by key or index
    if (key != NULL) {

        // Remove by key name
        entry = (psMetadataItem*)psHashLookup(mdTable, key);
        if (entry == NULL) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_FIND_FAILED, key);
            return false;
        }

    } else {

        // Remove by index
        entry = psListGet(mdList, where);
        if (entry == NULL) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_FIND_INDEX_FAILED, where);
            return false;
        }

        key = entry->name;
        if(key == NULL) {
            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_REMOVE_LIST_INDEX_FAILED, where);
            return false;
        }
    }

    if (!psListRemoveData(mdList, entry)) {
        psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_LIST_FAILED, key);
        return false;
    }

    // Remove entry from metadata collection's table
    if (!psHashRemove(mdTable, key)) {
        psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
        return false;
    }

    return true;
}

psMetadataItem* psMetadataLookup(psMetadata *md, const char *key)
{
    psHash* mdTable = NULL;
    psMetadataItem* entry = NULL;


    PS_PTR_CHECK_NULL(md,NULL);
    PS_PTR_CHECK_NULL(md->table,NULL);
    PS_PTR_CHECK_NULL(key,NULL);

    mdTable = md->table;
    entry = (psMetadataItem*)psHashLookup(mdTable, key);

    return entry;
}

void* psMetadataLookupPtr(psMetadata *md, const char *key, psBool *status)
{
    psMetadataItem *metadataItem = NULL;

    if (status) {
        *status = true;
    }

    metadataItem = psMetadataLookup(md, key);
    if(metadataItem == NULL) {
        if (status) {
            *status = false;
        }
        return NULL;
    }

    if(metadataItem->type == PS_META_PRIMITIVE && metadataItem->pType != PS_TYPE_PTR) {
        if (status) {
            *status = false;
        }
        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
                PS_ERRORTEXT_psMetadata_METATYPE_MISMATCH,
                metadataItem->pType,
                PS_TYPE_PTR);
        return NULL;
    } else {
        return metadataItem->data.V;
    }
}

#define psMetadataLookupNumTYPE(TYPE) \
ps##TYPE psMetadataLookup##TYPE(psMetadata *md, const char *key, psBool *status) \
{ \
    psMetadataItem *metadataItem = NULL; \
    \
    if (status) { \
        *status = true; \
    } \
    \
    metadataItem = psMetadataLookup(md, key); \
    if(metadataItem == NULL) { \
        if (status) { \
            *status = false; \
        } \
        return 0; \
    } \
    \
    if (metadataItem->type == PS_META_PRIMITIVE) { \
        switch (metadataItem->pType) { \
        case PS_TYPE_S32: \
            return metadataItem->data.S32; \
        case PS_TYPE_F32: \
            return metadataItem->data.F32; \
        case PS_TYPE_F64: \
            return metadataItem->data.F64; \
        case PS_TYPE_BOOL: \
            if (metadataItem->data.B) { \
                return 1; \
            } \
            return 0; \
        default: \
            break; \
        } \
    } \
    \
    /* if you get to this point, the value is not a number. */ \
    if (status) { \
        *status = false; \
    } \
    \
    return 0; \
}

psMetadataLookupNumTYPE(F32)
psMetadataLookupNumTYPE(F64)
psMetadataLookupNumTYPE(S32)
psMetadataLookupNumTYPE(Bool)

psMetadataItem* psMetadataGet(psMetadata *md, psS32 where)
{
    psMetadataItem* entry = NULL;

    PS_PTR_CHECK_NULL(md,NULL);
    PS_PTR_CHECK_NULL(md->list,NULL);

    entry = (psMetadataItem*) psListGet(md->list, where);
    if (entry == NULL) {
        psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_FIND_INDEX_FAILED, where);
        return NULL;
    }

    return entry;
}
