
/** @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.34 $ $Name: not supported by cvs2svn $
*  @date $Date: 2004-10-09 03:05:53 $
*
*  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 "psString.h"

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

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

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

// None

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

// None

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

static int 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);
    psFree(metadataItem->items);

    if (type == PS_META_STR || type == PS_META_VEC || type == PS_META_IMG || type == PS_META_JPEG ||
            type == PS_META_PNG || type == PS_META_ASTROM || type == PS_META_UNKNOWN) {
        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, 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, type, comment, argPtr);

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

    return metadataItem;
}

psMetadataItem* psMetadataItemAllocV(const char *name, psMetadataType type, const char *comment,
                                     va_list argPtr)
{
    psVector *vecIn = NULL;
    psVector *vecOut = NULL;
    psMetadataItem* metadataItem = NULL;


    if (name == NULL) {
        psError(__func__, "Null value for name not allowed");
        return NULL;
    }
    // Allocate metadata item
    metadataItem = (psMetadataItem* ) psAlloc(sizeof(psMetadataItem));

    // 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
    *(int *)(&metadataItem->id) = ++metadataId;

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

    // Set metadata item value
    switch (type) {
    case PS_META_ITEM_SET:
        metadataItem->data.V = NULL;
        break;
    case PS_META_BOOL:
        metadataItem->data.B = (bool)va_arg(argPtr, int);
        break;
    case PS_META_S32:
        metadataItem->data.S32 = (psS32)va_arg(argPtr, psS32);
        break;
    case PS_META_F32:
        metadataItem->data.F32 = (psF32)va_arg(argPtr, psF64);
        break;
    case PS_META_F64:
        metadataItem->data.F64 = va_arg(argPtr, psF64);
        break;
    case PS_META_STR:
        metadataItem->data.V = psStringNCopy(va_arg(argPtr, char *), MAX_STRING_LENGTH);
        break;
    case PS_META_VEC:
        vecIn = (psVector*)va_arg(argPtr, psVector*);
        vecOut = psVectorAlloc(vecIn->nalloc, vecIn->type.type);
        memcpy(vecOut->data.V, vecIn->data.V, vecIn->nalloc*PSELEMTYPE_SIZEOF(vecIn->type.type));
        metadataItem->data.V = vecOut;
        break;
    case PS_META_IMG:
    case PS_META_JPEG:
    case PS_META_PNG:
    case PS_META_ASTROM:
    case PS_META_UNKNOWN:
    default:
        psError(__func__, "Invalid psMetadataType: %d", type);
    }

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

    // Allocate metadata items with same name.
    metadataItem->items = psListAlloc(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;
}

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

    if(md == NULL) {
        psError( __func__, "Null metadata collection not allowed" );
        return false;
    }

    if(metadataItem == NULL) {
        psError( __func__, "Null metadata item not allowed" );
        return false;
    }

    newType = metadataItem->type;

    mdTable = md->table;
    if(mdTable == NULL) {
        psError( __func__, "Null metadata table not allowed" );
        return false;
    }

    mdList = md->list;
    if(mdList == NULL) {
        psError( __func__, "Null metadata list not allowed");
        return false;
    }

    key = metadataItem->name;
    if(key == NULL) {
        psError(__func__, "Null key item not allowed");
        return false;
    }

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

        if(existingEntry->type == PS_META_ITEM_SET) {

            // Add leaf node to existing folder node
            if(!psListAdd(existingEntry->items, PS_LIST_TAIL, metadataItem)) {
                psError( __func__, "Couldn't add metadata item to items list. Name: %s",
                         metadataItem->name );
                return false;
            }
        } else if(existingEntry->type != PS_META_ITEM_SET && newType!= PS_META_ITEM_SET) {

            // Leaf node replaces another leaf. Make new folder node and add leaves.
            newFolderEntry = psMetadataItemAlloc(key, PS_META_ITEM_SET, NULL, NULL);

            if(!psListAdd(newFolderEntry->items, PS_LIST_TAIL, existingEntry)) {
                psError( __func__, "Couldn't add metadata item to items list. Name: %s", key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psListAdd(newFolderEntry->items, PS_LIST_TAIL, metadataItem)) {
                psError( __func__, "Couldn't add metadata item to items list. Name: %s", key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psHashRemove(mdTable, key)) {
                psError( __func__, "Couldn't remove metadata item from metadata table. Name: %s", key);
                psFree(newFolderEntry);
                return false;
            }

            if(!psHashAdd(mdTable, key, newFolderEntry)) {
                psError( __func__, "Couldn't add metadata item to metadata table. Name: %s", 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(!psListAdd(metadataItem->items, PS_LIST_TAIL, existingEntry)) {
                psError( __func__, "Couldn't add metadata item to items list. Name: %s", key);
                return false;
            }

            if(!psHashRemove(mdTable, key)) {
                psError( __func__, "Couldn't remove metadata item from metadata table. Name: %s", key);
                return false;
            }

            if(!psHashAdd(mdTable, key, metadataItem)) {
                psError( __func__, "Couldn't add metadata item to metadata table. Name: %s", key);
                return false;
            }
        }
    } else {

        // Node doesn't exist - Add new metadata item to metadata collection's hash
        if(!psHashAdd(mdTable, key, metadataItem)) {
            psError( __func__, "Couldn't add metadata item to metadata table. Name: %s", key);
            return false;
        }
    }

    // Add items to metadata collection's list, even if they have the same metadata item names. Folder nodes
    // (PS_META_ITEM_SET metadata items) are not added, since the metadata list is flat.
    if(metadataItem->type != PS_META_ITEM_SET) {
        if(!psListAdd(mdList, location, metadataItem)) {
            psError( __func__, "Couldn't add metadata item to metadata collection list. Name: %s",
                     metadataItem->name );
            return false;
        }
    }

    return true;
}

bool psMetadataAdd(psMetadata* restrict md, int where, const char *name, psMetadataType type,
                   const char *comment, ...)
{
    va_list argPtr;
    psMetadataItem* metadataItem = NULL;

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

    if (!psMetadataAddItem(md, metadataItem, where)) {
        psError(__func__, "Couldn't add metadata item to metadata collection list. Name: %s",
                metadataItem->name);
        psFree(metadataItem);
        return false;
    }
    // Decrement reference count, since the metadata item is now in metadata collection and no longer needed
    psMemDecrRefCounter(metadataItem);

    return true;
}

bool psMetadataRemove(psMetadata* restrict md, int where, const char *restrict key)
{
    int numChildren = 0;
    psList* mdList = NULL;
    psHash* mdTable = NULL;
    psMetadataItem* entry = NULL;
    psMetadataItem* entryChild = NULL;


    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return false;
    }

    mdList = md->list;
    if (mdList == NULL) {
        psError(__func__, "Null metadata list not allowed");
        return false;
    }

    mdTable = md->table;
    if (mdTable == NULL) {
        psError(__func__, "Null metadata table not allowed");
        return false;
    }
    // Select removal by key or index
    if (key != NULL) {

        // Remove by key name
        entry = (psMetadataItem* ) psHashLookup(mdTable, key);
        if (entry == NULL) {
            psError(__func__, "Couldn't find metadata item. Name: %s", key);
            return false;
        }

        numChildren = entry->items->size;
        if (entry->type == PS_META_ITEM_SET && numChildren > 0) {

            // Table entry has children. Entry and children must be removed from metadata collection's list
            psListSetIterator(entry->items, PS_LIST_HEAD);
            entryChild = psListGetCurrent(entry->items);
            while (entryChild != NULL) {
                if (!psListRemove(mdList, PS_LIST_UNKNOWN, entryChild)) {
                    psError(__func__, "Couldn't remove metadata item from list. Name: %s", key);
                    return false;
                }
                entryChild = psListGetNext(entry->items);
            }
        } else {

            // Table entry has no children. Remove entry from metadata collection's list
            if (!psListRemove(mdList, PS_LIST_UNKNOWN, entry)) {
                psError(__func__, "Couldn't remove metadata item from list. Name: %s", key);
                return false;
            }
        }
        // Remove entry from metadata collection's table
        if (!psHashRemove(mdTable, key)) {
            psError(__func__, "Couldn't remove metadata item from table. Name: %s", key);
            return false;
        }
    } else {

        // Remove by index
        entry = psListGet(mdList, where);
        if (entry == NULL) {
            psError(__func__, "Couldn't find metadata item in list. Index: %d", where);
            return false;
        }

        key = entry->name;
        if (key == NULL) {
            psError(__func__, "Null key name not allowed. Index: %d", where);
            return false;
        }
        // Use recursive remove, now that key is known
        psMetadataRemove(md, PS_LIST_UNKNOWN, key);
    }

    return true;
}

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


    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return NULL;
    }

    mdTable = md->table;
    if (mdTable == NULL) {
        psError(__func__, "Null metadata table not allowed");
        return NULL;
    }

    if (key == NULL) {
        psError(__func__, "Null key name not allowed");
        return NULL;
    }

    entry = (psMetadataItem* ) psHashLookup(mdTable, key);
    if (entry == NULL) {
        return NULL;
    }

    return entry;
}

psMetadataItem* psMetadataGet(psMetadata* restrict md, int where)
{
    psList* mdList = NULL;
    psMetadataItem* entry = NULL;


    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return NULL;
    }

    mdList = md->list;
    if (mdList == NULL) {
        psError(__func__, "Null metadata list not allowed");
        return NULL;
    }

    entry = (psMetadataItem* ) psListGet(mdList, where);
    if (entry == NULL) {
        psError(__func__, "Couldn't find metadata item with given index. Index: %d", where);
        return NULL;
    }

    return entry;
}

bool psMetadataSetIterator(psMetadata* restrict md, int where)
{
    psList* mdList = NULL;

    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return false;
    }

    mdList = md->list;
    if (mdList == NULL) {
        psError(__func__, "Null metadata list not allowed");
        return false;
    }

    psListSetIterator(mdList, where);

    return true;
}

psMetadataItem* psMetadataGetNext(psMetadata* restrict md, const char *restrict match, int which)
{
    psList* mdList = NULL;
    psMetadataItem* entry = NULL;


    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return NULL;
    }

    mdList = md->list;
    if (mdList == NULL) {
        psError(__func__, "Null metadata list not allowed");
        return NULL;
    }

    psListSetIterator(mdList, which);
    entry = psListGetCurrent(mdList);
    while (entry != NULL) {
        if(match != NULL) {
            if (!strncmp(match, entry->name, strlen(match))) {

                // Match found
                return entry;
            }
            entry = psListGetNext(mdList);
        } else {
            entry = psListGetNext(mdList);
            break;
        }
    }

    return entry;
}

psMetadataItem* psMetadataGetPrevious(psMetadata* restrict md, const char *restrict match, int which)
{
    psList* mdList = NULL;
    psMetadataItem* entry = NULL;


    if (md == NULL) {
        psError(__func__, "Null metadata collection not allowed");
        return NULL;
    }

    mdList = md->list;
    if (mdList == NULL) {
        psError(__func__, "Null metadata list not allowed");
        return NULL;
    }

    psListSetIterator(mdList, which);
    entry = psListGetCurrent(mdList);
    while (entry != NULL) {
        if(match != NULL) {
            if (!strncmp(match, entry->name, strlen(match))) {

                // Match found
                return entry;
            }
            entry = psListGetPrevious(mdList);
        } else {
            entry = psListGetPrevious(mdList);
            break;
        }
    }

    return entry;
}
