/**
 *  C Implementation: tap_psMetadata_copying
 *
 * Description:  Tests for psMetadataCopy, psMetadataItemCopy, & psMetadataItemSupplement.
 *
 *
 * Author: dRob <David.Robbins@mhpcc.hpc.mil>, (C) 2006
 *
 * Copyright: See COPYING file that comes with this distribution
 *
 */

#include <pslib.h>
#include <string.h>

#include "tap.h"
#include "pstap.h"

void testMetadataCopy(void);
void testMetadataItemCopy(void);
void testMetadataItemTransfer(void);

int main(void)
{
    psLogSetFormat("HLNM");
    plan_tests(36);

    note("psMetadataCopy and psMetadataItemCopy tests");

    testMetadataCopy();
    testMetadataItemCopy();
    testMetadataItemTransfer();

    done();
}

void testMetadataCopy(void)
{
    note("  >>>Test 1:  psMetadataCopy");
    psMetadata *in = NULL;
    psMetadata *out = NULL;
    psMetadataItem *tempItem = NULL;
    psMetadataItem *outItem = NULL;
    psMetadataItem *inItem = NULL;
    psMetadata *outTest = NULL;

    //Return NULL for NULL psMetadata input
    {
        outTest = psMetadataCopy(out, NULL);
        ok( outTest == NULL,
            "psMetadataCopy:  return NULL for NULL input metadata.");
    }
    //Return allocated out for NULL psMetadata input (with allocated out)
    {
        out = psMetadataAlloc();
        outTest = psMetadataCopy(out, NULL);
        ok( outTest == NULL,
            "psMetadataCopy:  return out for NULL input metadata.");
        psFree(out);
        out = NULL;
    }
    //Return NULL for attempt to copy item of unsupported data type - NULL out metadata
    psMetadataItem *notype = NULL;
    {
        in = psMetadataAlloc();
        psMetadataAddS32(in, PS_LIST_HEAD, "S32", 0, "", 1);
        notype = psMetadataGet(in, PS_LIST_HEAD);
        notype->type = -1;
        outTest = psMetadataCopy(out, in);
        ok( outTest == NULL,
            "psMetadataCopy:  return NULL for metadata with item of bad type.");
        psFree(out);
        out = NULL;
    }
    //Return NULL for attempt to copy item of unsupported data type - allocated out metadata
    {
        out = psMetadataAlloc();
        psMetadataAddS32(out, PS_LIST_TAIL, "S32", 0, "", 2);
        outTest = psMetadataCopy(out, in);
        ok( outTest == NULL,
            "psMetadataCopy:  return NULL for metadata with item of bad type.");
        notype->type = PS_DATA_S32;
        psFree(out);
        out = NULL;
        psFree(in);
        in = NULL;
    }

    //Return NULL for NULL psMetadata input->list
    in = psMetadataAlloc();
    if (in->list != NULL) {
        psFree(in->list);
        in->list = NULL;
    }
    {
        outTest = psMetadataCopy(out, in);
        ok( outTest == NULL,
            "psMetadataCopy:  return NULL for NULL metadata input->list.");
    }
    /*    //Return NULL for NULL psMetadata input->list (with allocated out)
        {
            out = psMetadataAlloc();
            out = psMetadataCopy(out, in);
            ok( out == NULL,
                "psMetadataCopy:  return NULL for NULL metadata input->list.");
        }
    */
    //Return valid (but empty) metadata for NULL out input and empty input metadata.
    in->list = psListAlloc(NULL);
    psFree(in);
    in = psMetadataAlloc();
    if (out != NULL) {
        psFree(out);
        out = NULL;
    }
    outTest = psMetadataCopy(out, in);
    tempItem = psMetadataGet(in, PS_LIST_HEAD);
    {
        ok( outTest != NULL && tempItem == NULL,
            "psMetadataCopy:  return empty copy with no items for empty input metadata.");
        psFree(out);
        psFree(outTest);
        out = NULL;
        outTest = NULL;
    }

    //Test case: multiCheckItem->type == PS_DATA_METADATA_MULTI
    psMetadataAddS32(in, PS_LIST_HEAD, "S32", 0, "", 1);
    psMetadataAddS32(in, PS_LIST_TAIL, "S32", PS_META_DUPLICATE_OK, "", 1);
    outTest = psMetadataCopy(out, in);
    tempItem = psMetadataGet(outTest, PS_LIST_HEAD);
    {
        ok( tempItem->type == PS_DATA_S32 && tempItem->data.S32 == 1,
            "psMetadataCopy:  return valid copy for a PS_DATA_METADATA_MULTI entry.");
        psFree(outTest);
        outTest = NULL;
    }

    //Test case:  !psMetadataAddItem(out, newItem, PS_LIST_TAIL, flag)
    //XXX: doesn't seem to be easily reachable, certainly by the user...  Skipping.

    //Test case: simple valid test
    psFree(in);
    psFree(out);
    out = NULL;
    in = psMetadataAlloc();
    psMetadataAddS32(in, PS_LIST_HEAD, "S32", 0, "", 666);
    outTest = psMetadataCopy(out, in);
    outItem = psMetadataGet(outTest, PS_LIST_HEAD);
    inItem = psMetadataGet(in, PS_LIST_HEAD);
    {
        ok( outItem->type == PS_DATA_S32 && outItem->data.S32 == inItem->data.S32 &&
            (strncmp(inItem->name, outItem->name, 10) == 0),
            "psMetadataCopy:  return valid copy of metadata with 1 S32 entry.");
    }
    psFree(outTest);
    //Check for Memory Leaks
    {
        psFree(in);
        psFree(out);
        checkMem();
    }

}

void testMetadataItemCopy(void)
{
    note("  >>>Test 2:  psMetadataItemCopy");

    //Return NULL for NULL input psMetadataItem
    {
        ok( psMetadataItemCopy(NULL) == NULL,
            "psMetadataItemCopy:   return NULL for NULL input metadataItem.");
    }

    //Test Simple types:  Bool, S8, S16, S32, S64, U8, U16, U32, U64, F32, F64, STRING
    psMetadataItem *itemBool = psMetadataItemAlloc("itemBool", PS_DATA_BOOL, "", false);
    psMetadataItem *itemS8  =  psMetadataItemAlloc("itemS8", PS_DATA_S8, "", 1);
    psMetadataItem *itemS16 =  psMetadataItemAlloc("itemS16", PS_DATA_S16, "", 2);
    psMetadataItem *itemS32 =  psMetadataItemAlloc("itemS32", PS_DATA_S32, "", 3);
    psMetadataItem *itemS64 =  psMetadataItemAlloc("itemS64", PS_DATA_S64, "", 4);
    psMetadataItem *itemU8  =  psMetadataItemAlloc("itemU8", PS_DATA_U8, "", 1);
    psMetadataItem *itemU16 =  psMetadataItemAlloc("itemU16", PS_DATA_U16, "", 2);
    psMetadataItem *itemU32 =  psMetadataItemAlloc("itemU32", PS_DATA_U32, "", 3);
    psMetadataItem *itemU64 =  psMetadataItemAlloc("itemU64", PS_DATA_U64, "", 4);
    psMetadataItem *itemF32 =  psMetadataItemAlloc("itemF32", PS_DATA_F32, "", 3.0);
    psMetadataItem *itemF64 =  psMetadataItemAlloc("itemF64", PS_DATA_F64, "", 4.0);
    psMetadataItem *itemString = psMetadataItemAlloc("itemString", PS_DATA_STRING, "", "I M STRiNG");
    psMetadataItem *copy = NULL;
    //Bool case
    {
        copy = psMetadataItemCopy(itemBool);
        ok( (copy->type == PS_DATA_BOOL && (strncmp(copy->name, "itemBool", 10) == 0) &&
             !copy->data.B && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of BOOL item.");
        psFree(copy);
    }
    //S8 case
    {
        copy = psMetadataItemCopy(itemS8);
        ok( (copy->type == PS_DATA_S8 && (strncmp(copy->name, "itemS8", 10) == 0) &&
             copy->data.S8 == 1 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of S8 item.");
        psFree(copy);
    }
    //S16 case
    {
        copy = psMetadataItemCopy(itemS16);
        ok( (copy->type == PS_DATA_S16 && (strncmp(copy->name, "itemS16", 10) == 0) &&
             copy->data.S16 == 2 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of S16 item.");
        psFree(copy);
    }
    //S32 case
    {
        copy = psMetadataItemCopy(itemS32);
        ok( (copy->type == PS_DATA_S32 && (strncmp(copy->name, "itemS32", 10) == 0) &&
             copy->data.S32 == 3 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of S32 item.");
        psFree(copy);
    }
    //S64 case
    {
        copy = psMetadataItemCopy(itemS64);
        ok( (copy->type == PS_DATA_S64 && (strncmp(copy->name, "itemS64", 10) == 0) &&
             copy->data.S64 == 4 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of S64 item.");
        psFree(copy);
    }
    //U8 case
    {
        copy = psMetadataItemCopy(itemU8);
        ok( (copy->type == PS_DATA_U8 && (strncmp(copy->name, "itemU8", 10) == 0) &&
             copy->data.U8 == 1 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of U8 item.");
        psFree(copy);
    }
    //U16 case
    {
        copy = psMetadataItemCopy(itemU16);
        ok( (copy->type == PS_DATA_U16 && (strncmp(copy->name, "itemU16", 10) == 0) &&
             copy->data.U16 == 2 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of U16 item.");
        psFree(copy);
    }
    //U32 case
    {
        copy = psMetadataItemCopy(itemU32);
        ok( (copy->type == PS_DATA_U32 && (strncmp(copy->name, "itemU32", 10) == 0) &&
             copy->data.U32 == 3 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of U32 item.");
        psFree(copy);
    }
    //U64 case
    {
        copy = psMetadataItemCopy(itemU64);
        ok( (copy->type == PS_DATA_U64 && (strncmp(copy->name, "itemU64", 10) == 0) &&
             copy->data.U64 == 4 && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of U64 item.");
        psFree(copy);
    }
    //F32 case
    {
        copy = psMetadataItemCopy(itemF32);
        ok( (copy->type == PS_DATA_F32 && (strncmp(copy->name, "itemF32", 10) == 0) &&
             abs(copy->data.F32-3.0) < FLT_EPSILON && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of F32 item.");
        psFree(copy);
    }
    //F64 case
    {
        copy = psMetadataItemCopy(itemF64);
        ok( (copy->type == PS_DATA_F64 && (strncmp(copy->name, "itemF64", 10) == 0) &&
             abs(copy->data.F64-4.0) < DBL_EPSILON && strncmp(copy->comment, "", 2) == 0),
            "psMetadataItemCopy:  return valid copy of F64 item.");
        psFree(copy);
    }
    //STRING case
    {
        copy = psMetadataItemCopy(itemString);
        ok( (copy->type == PS_DATA_STRING && (strncmp(copy->name, "itemString", 12) == 0) &&
             strncmp(copy->data.str,"I M STRiNG",12) == 0 && strncmp(copy->comment,"",2) == 0),
            "psMetadataItemCopy:  return valid copy of STRING item.");
        psFree(copy);
    }

    //Test Remaining types:  Vector, Time, Metadata, Region
    psVector *inV = psVectorAlloc(2, PS_TYPE_S32);
    psVectorSet(inV, 0, 666);
    psVectorSet(inV, 1, 667);
    psMetadataItem *itemVector = psMetadataItemAlloc("itemVector", PS_DATA_VECTOR, "", inV);
    psTime *teaTime = psTimeAlloc(PS_TIME_TAI);
    teaTime->sec = 1;
    teaTime->nsec = 0;
    teaTime->leapsecond = true;
    psMetadataItem *itemTime = psMetadataItemAlloc("itemTime", PS_DATA_TIME, "", teaTime);
    psMetadata *md = psMetadataAlloc();
    psMetadataAddS32(md, PS_LIST_HEAD, "mdS32", 0, "", 666);
    psMetadataItem *itemMD = psMetadataItemAlloc("itemMD", PS_DATA_METADATA, "", md);
    psRegion *reg = psRegionAlloc(0.0, 2.0, 1.0, 3.0);
    psMetadataItem *itemRegion = psMetadataItemAlloc("itemRegion", PS_DATA_REGION, "", reg);
    //Vector case
    {
        copy = psMetadataItemCopy(itemVector);
        int int0 = ((psVector*)(copy->data.V))->data.S32[0];
        int int1 = ((psVector*)(copy->data.V))->data.S32[1];
        ok( (copy->type == PS_DATA_VECTOR && (strncmp(copy->name, "itemVector", 12) == 0)
             && int0 == 666 && int1 == 667 && strncmp(copy->comment,"",2) == 0),
            "psMetadataItemCopy:  return valid copy of Vector item.");
        psFree(copy);
    }
    //Vector case - invalid vector item
    {
        psFree(itemVector->data.V);
        itemVector->data.V = NULL;
        copy = psMetadataItemCopy(itemVector);
        ok( copy == NULL, "psMetadataItemCopy:  return NULL for invalid vector item");
        //        itemVector->data.V = psVectorAlloc(1, PS_TYPE_S32);
    }
    //Time case
    {
        copy = psMetadataItemCopy(itemTime);
        int int0 = (int)((psTime*)(copy->data.V))->sec;
        int int1 = (int)((psTime*)(copy->data.V))->nsec;
        bool leap = ((psTime*)(copy->data.V))->leapsecond;
        ok( (copy->type == PS_DATA_TIME && (strncmp(copy->name, "itemTime", 12) == 0)
             && int0 == 1 && int1 == 0 && strncmp(copy->comment,"",2) == 0) && leap,
            "psMetadataItemCopy:  return valid copy of Time item.");
        psFree(copy);
    }
    //Time case - invalid time item
    {
        psTime *t = psTimeAlloc(PS_TIME_TT);
        t->type += 1;
        psMetadataItem *itemTime2 = psMetadataItemAlloc("itemTime", PS_DATA_TIME, "", t);
        copy = psMetadataItemCopy(itemTime2);
        ok( copy != NULL && copy->data.V == NULL,
            "psMetadataItemCopy:  return non-NULL item for invalid input time item");
        psFree(t);
        psFree(copy);
        psFree(itemTime2);
    }
    //Time case - NULL time item
    {
        psFree(itemTime->data.V);
        itemTime->data.V = NULL;
        copy = psMetadataItemCopy(itemTime);
        ok( copy != NULL && copy->data.V == NULL,
            "psMetadataItemCopy:  return non-NULL item for NULL input time item");
        //        (itemTime->data.V) = psTimeAlloc(PS_TIME_TAI);
        psFree(copy);
    }
    //Metadata case
    {
        copy = psMetadataItemCopy(itemMD);
        psMetadataItem *testItem = NULL;
        testItem = psMetadataGet(((psMetadata*)(copy->data.V)), PS_LIST_HEAD);
        if (testItem == NULL)
            printf("\nError in psMetadataGet.\n");
        ok( copy->type == PS_DATA_METADATA && strncmp(copy->name, "itemMD", 8) == 0
            && (strncmp(copy->comment, "",2) == 0) && testItem->type == PS_DATA_S32
            && (strncmp(testItem->name, "mdS32", 10) == 0) && testItem->data.S32 == 666
            && (strncmp(testItem->comment,"",2) == 0),
            "psMetadataItemCopy:  return valid copy of Metadata item.");
        psFree(copy);
    }
    //Region case
    {
        copy = psMetadataItemCopy(itemRegion);
        float x0 = ((psRegion*)(copy->data.V))->x0;
        float x1 = ((psRegion*)(copy->data.V))->x1;
        float y0 = ((psRegion*)(copy->data.V))->y0;
        float y1 = ((psRegion*)(copy->data.V))->y1;
        ok( (copy->type == PS_DATA_REGION && (strncmp(copy->name, "itemRegion", 12) == 0)
             && abs(x0-0.0) < FLT_EPSILON && abs(x1-2.0) < FLT_EPSILON
             && abs(y0-1.0) < FLT_EPSILON && abs(y1-3.0) < FLT_EPSILON
             && strncmp(copy->comment,"",2) == 0),
            "psMetadataItemCopy:  return valid copy of Region item.");
        psFree(copy);
    }

    //Test Unsupported type - (sphere for example)
    psSphere *sphere = psSphereAlloc();
    sphere->r = 1.0;
    sphere->d = 6.66;
    psMetadataItem *itemSphere = psMetadataItemAlloc("itemSphere", PS_DATA_SPHERE, "", sphere);
    psFree(sphere);
    {
        copy = psMetadataItemCopy(itemSphere);
        float R = ((psSphere*)(copy->data.V))->r;
        float D = ((psSphere*)(copy->data.V))->d;
        ok( (copy->type == PS_DATA_SPHERE && (strncmp(copy->name, "itemSphere", 12) == 0) &&
             abs(R - 1.0) < FLT_EPSILON && abs(D - 6.66) < FLT_EPSILON &&
             strncmp(copy->comment,"",2) == 0),
            "psMetadataItemCopy:  return valid copy of SPHERE item.");
        psFree(copy);
    }

    //Check for Memory Leaks before exit
    {
        psFree(itemSphere);
        psFree(inV);
        psFree(teaTime);
        psFree(md);
        psFree(reg);
        psFree(itemBool);
        psFree(itemS8);
        psFree(itemS16);
        psFree(itemS32);
        psFree(itemS64);
        psFree(itemU8);
        psFree(itemU16);
        psFree(itemU32);
        psFree(itemU64);
        psFree(itemF32);
        psFree(itemF64);
        psFree(itemString);
        psFree(itemVector);
        psFree(itemTime);
        psFree(itemMD);
        psFree(itemRegion);
        checkMem();
    }

}

void testMetadataItemTransfer(void)
{
    note("  >>>Test 3:  psMetadataItemSupplement");
    psMetadata *out = psMetadataAlloc();
    psMetadata *in = psMetadataAlloc();
    psString key = psStringAlloc(30);
    strncpy(key, "key", 4);

    //Return false for NULL in input
    {
        ok( !psMetadataItemSupplement(out, NULL, key),
            "psMetadataItemSupplement:  return false for NULL in metadata.");
    }
    //Return false for NULL out input
    {
        ok( !psMetadataItemSupplement(NULL, in, key),
            "psMetadataItemSupplement:  return false for NULL out metadata.");
    }
    //Return false for NULL key input
    {
        ok( !psMetadataItemSupplement(out, in, NULL),
            "psMetadataItemSupplement:  return false for NULL key string.");
    }
    psMetadataAddS32(in, PS_LIST_HEAD, "key", PS_META_NO_REPLACE, "", 666);
    psMetadataAddF32(out, PS_LIST_HEAD, "out", 0, "", 6.66);

    //Return true for valid inputs
    {
        bool transfered = psMetadataItemSupplement(out, in, key);
        psMetadataItem *item = NULL;
        if (transfered)
            item = psMetadataGet(out, PS_LIST_TAIL);
        ok(transfered && item->type == PS_DATA_S32 && strncmp(item->name, "key", 4) == 0
           && item->data.S32 == 666 && strncmp(item->comment, "", 2) == 0,
           "psMetadataItemSupplement:  return true for valid inputs.");
    }
    /*  Attempt here was to return false inside of ItemTransfer at psMetadataAddItem.
    //Return false for duplicate entry with PS_META_DEFAULT (no duplicates)
    {
        psMetadataRemoveKey(out, key);
        psMetadataItem *item = psMetadataGet(in, PS_LIST_HEAD);
        psMetadataAddItem(out, item, PS_LIST_TAIL, PS_META_NO_REPLACE);
        ok( !psMetadataItemSupplement(out, in, key),
            "psMetadataItemSupplement:  return false for duplicate metadataItem.");
    }
    */
    //Return false for invalid key
    {
        strncpy(key, "not MY key", 15);
        ok( !psMetadataItemSupplement(out, in, key),
            "psMetadataItemSupplement:  return false for invalid key.");
    }
    //Return false for invalid metadata
    psMetadata *invalid = psMetadataAlloc();
    psFree(invalid->hash);
    invalid->hash = NULL;
    {
        ok( !psMetadataItemSupplement(invalid, in, "key"),
            "psMetadataItemSupplement:  return false for invalid metadata.");
    }
    psFree(invalid);

    //Check for Memory Leaks
    {
        psFree(out);
        psFree(in);
        psFree(key);
        checkMem();
    }

}
