/** @file  tst_psTime_03.c
 *
 *  @brief Test driver for psTime functions
 *
 *  This test driver contains the following tests for psTime:
 *   1) psTimeMath invalid times
 *   2) psTimeMath valid time of different types
 *   3) psTimeDelta valid times with different types
 *
 *  @author  Ross Harman, MHPCC
 *  @author  Eric Van Alst, MHPCC
 *
 *  @version $Revision: 1.3 $  $Name: not supported by cvs2svn $
 *  @date  $Date: 2005-12-05 22:00:48 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
 *
 */

#include "pslib_strict.h"
#include "psTest.h"
#include <string.h>

#define ERROR_TOL    0.001

static psS32 testTimeMath(void);
static psS32 testTimeDelta(void);
static psS32 testTimeConvert1(void);

// Test Time 1 : May 9, 2005 00:00:00,0
//               MJD = 53499.00
//               JD = 2453499.5
// UTC Test Time 1
const psS64 testTime1SecondsUTC         = 1115596900;
const psU32 testTime1NanosecondsUTC     = 0;
// TAI Test Time 1
const psS64 testTime1SecondsTAI         = 1115596932;
const psU32 testTime1NanosecondsTAI     = 0;
// TT Test Time 1
const psS64 testTime1SecondsTT          = 1115596964;
const psU32 testTime1NanosecondsTT      = 184000000;
// UT1 Test Time 1
const psS64 testTime1SecondsUT1         = 1115596900;
const psU32 testTime1NanosecondsUT1     = 184000000;
// Delta 1
const psF64 deltaTime1                  = -15.5;
// Expected UTC Time 1
const psS64 newTestTime1SecondsUTC      = 1115596884;
const psU32 newTestTime1NanosecondsUTC  = 500000000;
// Expected TAI Time 1
const psS64 newTestTime1SecondsTAI      = 1115596916;
const psU32 newTestTime1NanosecondsTAI  = 500000000;
// Delta 2
const psF64 deltaTime2                  = 123.066;
// Expected TT Time 1 w/ delta 2
const psS64 newTestTime1SecondsTT       = 1115597087;
const psU32 newTestTime1NanosecondsTT   = 250000000;
// Expected UT1 Time 1 w/ delta 2
const psS64 newTestTime1SecondsUT1      = 1115597023;
const psU32 newTestTime1NanosecondsUT1  = 250000000;

// Test Time 2 : Dec. 31 1998 23:59:45,0
//               MJD = 51178.99983
//               JD = 2451179.49983
// UTC Test Time 1
const psS64 testTime2SecondsUTC         = 915148785;
const psU32 testTime2NanosecondsUTC     = 0;
// Delta 3
const psF64 deltaTime3                    = 30.0;
// Expected UTC Time
const psS64 newTestTime2SecondsUTC      = 915148814;
const psU32 newTestTime2NanosecondsUTC  = 0;

// Appendix B time conversion tests
//
#define APPB_TESTS    8
const psS64 testTimeBSeconds[APPB_TESTS] =
    {
        915148829,
        915148829,
        915148830,
        915148830,
        915148831,
        915148831,
        915148832,
        915148832
    };
const psU32 testTimeBNanoseconds[APPB_TESTS] =
    {
        0,
        500000000,
        0,
        500000000,
        0,
        500000000,
        0,
        500000000
    };
const psBool testTimeBLeapsecond[APPB_TESTS] =
    {
        false,
        false,
        false,
        false,
        true,
        true,
        false,
        false
    };
// Expected results
const char* testTimeBStrUTC[APPB_TESTS] =
    {
        "1998-12-31T23:59:58.0Z",
        "1998-12-31T23:59:58.5Z",
        "1998-12-31T23:59:59.0Z",
        "1998-12-31T23:59:59.5Z",
        "1998-12-31T23:59:60.0Z",
        "1998-12-31T23:59:60.5Z",
        "1999-01-01T00:00:00.0Z",
        "1999-01-01T00:00:00.5Z"
    };
const char* testTimeBStrTAI[APPB_TESTS] =
    {
        "1999-01-01T00:00:29.0Z",
        "1999-01-01T00:00:29.5Z",
        "1999-01-01T00:00:30.0Z",
        "1999-01-01T00:00:30.5Z",
        "1999-01-01T00:00:31.0Z",
        "1999-01-01T00:00:31.5Z",
        "1999-01-01T00:00:32.0Z",
        "1999-01-01T00:00:32.5Z"
    };
const char* testTimeBStrTT[APPB_TESTS] =
    {
        "1999-01-01T00:01:01.1Z",
        "1999-01-01T00:01:01.6Z",
        "1999-01-01T00:01:02.1Z",
        "1999-01-01T00:01:02.6Z",
        "1999-01-01T00:01:03.1Z",
        "1999-01-01T00:01:03.6Z",
        "1999-01-01T00:01:04.1Z",
        "1999-01-01T00:01:04.6Z"
    };
const char* testTimeBStrUT1[APPB_TESTS] =
    {
        "1998-12-31T23:59:58.7Z",
        "1998-12-31T23:59:59.2Z",
        "1998-12-31T23:59:59.7Z",
        "1998-12-31T23:59:60.2Z",
        "1998-12-31T23:59:60.7Z",
        "1999-01-01T00:00:00.2Z",
        "1999-01-01T00:00:00.7Z",
        "1999-01-01T00:00:01.2Z"
    };

// Test Time B1 : Dec 31, 1998 23:59:58,0
//                MJD = 51178.99998
//                JD = 2451179.49998
//const psS64 testTimeB1SecondsUTC        = 915148798;
//const psU32 testTimeB1NanosecondsUTC    = 0;
// Expected ISO times
//const char testTimeB1StrUTC[] = "1998-12-31T23:59:58,0Z";
//const char testTimeB1StrTAI[] = "1999-01-01T00:00:29,0Z";
//const char testTimeB1StrTT[]  = "1999-01-01T00:01:01,1Z";
//const char testTimeB1StrUT1[] = "1998-12-31T23:59:57,7Z";
//
// Test Time B2 : Dec 31, 1998 23:59:58,5
//
//
//const psS64 testTimeB2SecondsUTC       = 915148798;
//const psU32 testTimeB2NanosecondsUTC   = 500000000;
// Expected ISO times
//const char testTimeB2StrUTC[] = "1998-12-31T23:59:58,5Z";
//const char testTimeB2StrTAI[] = "1991-01-01T00:00:29,5Z";
//const char testTimeB2StrTT[]  = "1999-01-01T00:01:01,6Z";
//const char testTimeB2StrUT1[] = "1998-12-31T23:59:58,2Z";

testDescription tests[] = {
                              {testTimeMath,000,"psTimeMath",0,false},
                              {testTimeDelta,000,"psTimeDelta",0,false},
                              {testTimeConvert1,000,"psTimeConvert",0,false},
                              {NULL}
                          };

psS32 main(psS32 argc, char* argv[])
{
    psLogSetLevel(PS_LOG_INFO);

    // Initialize library internal structures
    psLibInit("pslib.config");

    if( !runTestSuite(stderr,"psTime",tests,argc,argv)) {
        return 1;
    }

    // Clean up library
    psLibFinalize();

    return 0;
}

psS32 testTimeMath(void)
{
    psTime*     time      = NULL;
    psTime*     newTime   = NULL;

    // Attempt to perform math operation on NULL time
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for NULL time");
    if(psTimeMath(time,-1.1) != NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return NULL for NULL time");
        return 1;
    }

    // Set up input time with invalid nanoseconds
    time = psTimeAlloc(PS_TIME_UTC);
    time->sec = 0;
    time->nsec = 2e9;
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for invalid time");
    if(psTimeMath(time,-1.1) != NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return NULL for invalid time");
        return 2;
    }

    // Set up input time with valid time
    time->sec = testTime1SecondsUTC;
    time->nsec = testTime1NanosecondsUTC;
    newTime = psTimeMath(time,deltaTime1);
    if(newTime == NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not expect NULL return");
        return 3;
    }
    if(newTime->type != PS_TIME_UTC) {
        psError(PS_ERR_UNKNOWN,true,"New time type %d not as expected %d",
                newTime->type,PS_TIME_UTC);
        return 4;
    }
    if((newTime->sec != newTestTime1SecondsUTC) || (newTime->nsec != newTestTime1NanosecondsUTC)) {
        psError(PS_ERR_UNKNOWN,true,"UTC sec %ld nsec %d not as expected sec %ld nsec %d",
                (long int)newTime->sec,newTime->nsec,(long int)newTestTime1SecondsUTC,
                newTestTime1NanosecondsUTC);
        return 5;
    }
    psFree(newTime);

    // Set up input time with valid TAI time
    time->sec = testTime1SecondsTAI;
    time->nsec = testTime1NanosecondsTAI;
    time->type = PS_TIME_TAI;
    newTime = psTimeMath(time,deltaTime1);
    if(newTime == NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not expect NULL return");
        return 3;
    }
    if(newTime->type != PS_TIME_TAI) {
        psError(PS_ERR_UNKNOWN,true,"New time type %d not as expected %d",
                newTime->type,PS_TIME_TAI);
        return 4;
    }
    if((newTime->sec != newTestTime1SecondsTAI) || (newTime->nsec != newTestTime1NanosecondsTAI)) {
        psError(PS_ERR_UNKNOWN,true,"TAI sec %ld nsec %d not as expected sec %ld nsec %d",
                (long int)newTime->sec,newTime->nsec,(long int)newTestTime1SecondsTAI,
                newTestTime1NanosecondsTAI);
        return 5;
    }
    psFree(newTime);

    // Set up input time with valid TT time
    time->sec = testTime1SecondsTT;
    time->nsec = testTime1NanosecondsTT;
    time->type = PS_TIME_TT;
    newTime = psTimeMath(time,deltaTime2);
    if(newTime == NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not expect NULL return");
        return 3;
    }
    if(newTime->type != PS_TIME_TT) {
        psError(PS_ERR_UNKNOWN,true,"New time type %d not as expected %d",
                newTime->type,PS_TIME_TT);
        return 4;
    }
    if((newTime->sec != newTestTime1SecondsTT) || (newTime->nsec != newTestTime1NanosecondsTT)) {
        psError(PS_ERR_UNKNOWN,true,"TT sec %ld nsec %d not as expected sec %ld nsec %d",
                (long int)newTime->sec,newTime->nsec,(long int)newTestTime1SecondsTT,
                newTestTime1NanosecondsTT);
        return 5;
    }
    psFree(newTime);

    // Set up input time with valid TT time
    time->sec = testTime1SecondsUT1;
    time->nsec = testTime1NanosecondsUT1;
    time->type = PS_TIME_UT1;
    newTime = psTimeMath(time,deltaTime2);
    if(newTime == NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not expect NULL return");
        return 3;
    }
    if(newTime->type != PS_TIME_UT1) {
        psError(PS_ERR_UNKNOWN,true,"New time type %d not as expected %d",
                newTime->type,PS_TIME_UT1);
        return 4;
    }
    if((newTime->sec != newTestTime1SecondsUT1) || (newTime->nsec != newTestTime1NanosecondsUT1)) {
        psError(PS_ERR_UNKNOWN,true,"UT1 sec %ld nsec %d not as expected sec %ld nsec %d",
                (long int)newTime->sec,newTime->nsec,(long int)newTestTime1SecondsUT1,
                newTestTime1NanosecondsUT1);
        return 5;
    }
    psFree(newTime);

    // Set up input time with valid UTC time and delt which crosses leap second
    time->sec = testTime2SecondsUTC;
    time->nsec = testTime2NanosecondsUTC;
    time->type = PS_TIME_UTC;
    newTime = psTimeMath(time,deltaTime3);
    if(newTime == NULL) {
        psError(PS_ERR_UNKNOWN,true,"Did not expect NULL return");
        return 3;
    }
    if(newTime->type != PS_TIME_UTC) {
        psError(PS_ERR_UNKNOWN,true,"New time type %d not as expected %d",
                newTime->type,PS_TIME_UTC);
        return 4;
    }
    if((newTime->sec != newTestTime2SecondsUTC) || (newTime->nsec != newTestTime2NanosecondsUTC)) {
        psError(PS_ERR_UNKNOWN,true,"UTC sec %ld nsec %d not as expected sec %ld nsec %d",
                (long int)newTime->sec,newTime->nsec,(long int)newTestTime2SecondsUTC,
                newTestTime2NanosecondsUTC);
        return 5;
    }
    psFree(newTime);

    psFree(time);

    return 0;
}

psS32 testTimeDelta(void)
{
    psTime*   time1   = NULL;
    psTime*   time2   = NULL;
    psF64     delta   = 0.0;

    // Attempt to get delta with time1 NULL
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for NULL time");
    delta = psTimeDelta(time1,time2);
    if(fabs(delta-0.0) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return 0 for NULL time argument");
        return 1;
    }

    // Set time 1
    time1 = psTimeAlloc(PS_TIME_UTC);

    // Attempt to get delta with time2 NULL
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for NULL time");
    delta = psTimeDelta(time1,time2);
    if(fabs(delta-0.0) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return 0 for NULL time argument");
        return 2;
    }

    // Set time 2
    time2 = psTimeAlloc(PS_TIME_UTC);

    // Set time1 invalid
    time1->sec = 0;
    time1->nsec = 2e9;
    // Attempt to get delta with time1 invalid
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for invalid time");
    delta = psTimeDelta(time1,time2);
    if(fabs(delta-0.0) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return 0 for invalid time argument");
        return 3;
    }

    // Set time 1 valid
    time1->sec = testTime1SecondsUTC;
    time1->nsec = testTime1NanosecondsUTC;

    // Set time 2 invalid
    time2->sec = 0;
    time2->nsec = 2e9;
    // Attempt to get delta with time2 invalid
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for invalid time");
    delta = psTimeDelta(time1,time2);
    if(fabs(delta-0.0) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return 0 for invalid time argument");
        return 4;
    }

    // Set time 2 valid but different type
    time2->sec = newTestTime1SecondsUTC;
    time2->nsec = newTestTime1NanosecondsUTC;
    time2->type = PS_TIME_TAI;
    // Attempt to get delta with different time types
    psLogMsg(__func__,PS_LOG_INFO,"Following should generate error message for incorrect type");
    delta = psTimeDelta(time1,time2);
    if(fabs(delta-0.0) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Did not return 0 for invalid time argument");
        return 5;
    }

    // Set time 2 to same as time 1
    time2->type = PS_TIME_UTC;
    // Attempt to get delta with valid times of the same type
    delta = psTimeDelta(time2,time1);
    if(fabs(delta-deltaTime1) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Delta %lf not as expected %lf",
                delta,deltaTime1);
        return 6;
    }

    // Set time 1 and 2 as different times with same type
    time1->sec = testTime1SecondsTT;
    time1->nsec = testTime1NanosecondsTT;
    time1->type = PS_TIME_TT;
    time2->sec = newTestTime1SecondsTT;
    time2->nsec = newTestTime1NanosecondsTT;
    time2->type = PS_TIME_TT;
    // Attempt to get delta with valid times of the same type
    delta = psTimeDelta(time2,time1);
    if(fabs(delta-deltaTime2) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Delta %lf not as expected %lf",
                delta,deltaTime2);
        return 7;
    }

    // Set time 1 and 2 as different times with same type
    time1->sec = testTime2SecondsUTC;
    time1->nsec = testTime2NanosecondsUTC;
    time1->type = PS_TIME_UTC;
    time2->sec = newTestTime2SecondsUTC;
    time2->nsec = newTestTime2NanosecondsUTC;
    time2->type = PS_TIME_UTC;
    // Attempt to get delta with valid times of the same type
    delta = psTimeDelta(time2,time1);
    if(fabs(delta-deltaTime3) > ERROR_TOL) {
        psError(PS_ERR_UNKNOWN,true,"Delta %lf not as expected %lf",
                delta,deltaTime3);
        return 7;
    }

    psFree(time1);
    psFree(time2);

    return 0;
}

psS32 testTimeConvert1(void)
{
    psTime*     time    = NULL;
    char*       timeStr = NULL;

    for(psS32 i = 0; i < APPB_TESTS; i++) {

        // Initialize time for test time B1
        time = psTimeAlloc(PS_TIME_TAI);
        time->sec = testTimeBSeconds[i];
        time->nsec = testTimeBNanoseconds[i];

        // Verify TAI ISO string
        timeStr = psTimeToISO(time);
        if(strcmp(timeStr,testTimeBStrTAI[i]) != 0) {
            psError(PS_ERR_UNKNOWN,true,"TAI ISO string %s not as expected %s",timeStr,testTimeBStrTAI[i]);
            return i+1;
        }
        psFree(timeStr);

        time = psTimeConvert(time,PS_TIME_TT);
        timeStr = psTimeToISO(time);
        if(strcmp(timeStr,testTimeBStrTT[i]) != 0) {
            psError(PS_ERR_UNKNOWN,true,"TT ISO string %s not as expected %s",timeStr,testTimeBStrTT[i]);
            return i+10;
        }
        psFree(timeStr);

        // Verify UTC ISO string
        time = psTimeConvert(time,PS_TIME_UTC);
        time->leapsecond = testTimeBLeapsecond[i];
        timeStr = psTimeToISO(time);
        if(strcmp(timeStr,testTimeBStrUTC[i]) != 0) {
            psError(PS_ERR_UNKNOWN,true,"UTC ISO string %s not as expected %s",timeStr,testTimeBStrUTC[i]);
            return i+20;
        }
        psFree(timeStr);

        time = psTimeConvert(time,PS_TIME_UT1);
        timeStr = psTimeToISO(time);
        if(strcmp(timeStr,testTimeBStrUT1[i]) != 0) {
            psError(PS_ERR_UNKNOWN,true,"UT1 ISO string %s not as expected %s",timeStr,testTimeBStrUT1[i]);
            return i+30;
        }
        psFree(timeStr);

        psFree(time);
    }

    return 0;
}

