/** \file
 *
 *  Contains unit tests for the Timestamp and Timespan classes
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */

#ifndef _TC_TIME_H_
#define _TC_TIME_H_

#include "utils/Timestamp.h"
#include <cxxtest/TestSuite.h>

#include <time.h>

const double DOUBLE_FUDGE = 1e-6;
const Timespan TIMESPAN_FUDGE( DOUBLE_FUDGE );

/**
 *  Unit Tests for class Timestamp
 *
 *  \ingroup utils
 */
class Timestamp_Test : public CxxTest::TestSuite
{
public:

    // Constructor with double param tests
    /// Tests:
    /// -# Create a new object w/o params.  Expect asDouble() == 0
    /// -# Create a new object with double param, Expect asDouble() == param
    void testConstructorNoArguments( void )
    {
        Timestamp thisTimestamp;

        // Check both ways
        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), Timestamp::NOT_SET_TIME.asDouble() );
        TS_ASSERT_EQUALS( thisTimestamp, Timestamp( Timestamp::NOT_SET_TIME ) );

        // Should fail...
        TS_ASSERT_DIFFERS( thisTimestamp.asDouble(), 0.0 );
        TS_ASSERT_DIFFERS( thisTimestamp, Timestamp( 0.0 ) );
    }

    void testConstructorStringArg( void )
    {
        Timestamp thisTimestamp( "1970-01-01T00:00:00.000000Z" );

        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), 0 );

        thisTimestamp = "1970-01-01T00:00:00.015625Z";
        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), 0.015625 );

        thisTimestamp = "2009-02-13T23:31:30Z";
        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), 1234567890.0 );

        thisTimestamp = "02/13/2009 23:31:30";
        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), 1234567890.0 );

    }

    void testConstructorDoubleArg( void )
    {
        double param = 100.0;
        Timestamp thisTimestamp( param );

        TS_ASSERT_EQUALS( thisTimestamp.asDouble(), param );
        TS_ASSERT_EQUALS( thisTimestamp, Timestamp( param ) );
    }

    /// Constructor with copy param tests
    /// -# Create new object copied from another object.  asDouble == asDouble
    /// -# Create new object copied from another object.  Equal with == operator
    void testConstructorCopyArg( void )
    {
        Timestamp timestampOne( 100.0 ), timestampTwo( timestampOne );

        TS_ASSERT_EQUALS( timestampOne.asDouble(), timestampTwo.asDouble() );
        TS_ASSERT_EQUALS( timestampOne, timestampTwo );
    }

    /// Copy operator.
    ///
    /// Test:
    /// -# Create two objects with different double params.  Test they are
    ///   different.  Use copy to set one to the other.  Test they are same
    ///   with equality operator.
    void testCopyOperator( void )
    {
        Timestamp timestampOne( 100.0 ), timestampTwo( 1234567890.0 );

        TS_ASSERT_DIFFERS( timestampOne, timestampTwo );
        timestampOne = timestampTwo;
        TS_ASSERT_EQUALS( timestampOne, timestampTwo );
    }

    /// Set operator
    ///
    /// Test:
    /// -# Create object with double param A.  Test asDouble is not equal to B.
    ///    Use operator to set to B.  Test asDouble is equal to B
    /// -# Create two objects with different double params.  Test they are
    ///   different.  Use copy to set one to the other's asDouble.  Test they are same
    ///   with equality operator.
    void testSetOperatorOne( void )
    {
        double A = 123456.0, B = 2042322342.0;

        Timestamp timestampOne( A );

        TS_ASSERT_DIFFERS( timestampOne.asDouble(), B );
        TS_ASSERT_DIFFERS( timestampOne, Timestamp( B ) );
        timestampOne = B;
        TS_ASSERT_EQUALS( timestampOne.asDouble(), B );
        TS_ASSERT_EQUALS( timestampOne, Timestamp( B ) );
    }

    void testSetOperatorTwo( void )
    {
        Timestamp timestampOne( 2042322342.0 ), timestampTwo( 1.0 );

        TS_ASSERT_DIFFERS( timestampOne, timestampTwo );
        timestampOne = timestampTwo.asDouble();
        TS_ASSERT_EQUALS( timestampOne, timestampTwo );
    }

    /// In-place addition operator to timespan
    /// Tests:
    /// -# Create to timestamp objects with params A timespan B.  Sum objects A+=B.
    ///    Verify (A+B).asDouble() == (A.asDouble + B.asDouble )
    void testInPlaceAdditionToTimespan( void )
    {
        double A = 12345.6, B = 92245.0;
        Timestamp timestampOne( A );
        Timespan timespanTwo( B );

        timestampOne += timespanTwo;
        TS_ASSERT_DELTA( timestampOne.asDouble(), A + B, DOUBLE_FUDGE );
        TS_ASSERT_DELTA( timestampOne, Timestamp( A + B ), TIMESPAN_FUDGE );

    }

    /// In-place subtraction operator to timespan
    /// Tests:
    /// -# Create to timestamp objects with params A timespan B.  Subtract objects A-=B.
    ///    Verify (A-B).asDouble() == (A.asDouble - B.asDouble )
    void testInPlaceSubtractionByTimespan( void )
    {
        double A = 12345.6, B = 92245.0;
        Timestamp timestampOne( A );
        Timespan timespanTwo( B );

        timestampOne -= timespanTwo;

        TS_ASSERT_DELTA( timestampOne.asDouble(), A - B, DOUBLE_FUDGE );
        TS_ASSERT_DELTA( timestampOne, Timestamp( A - B ), TIMESPAN_FUDGE );

    }

    /// Standard addition/subtraction functions.
    ///
    /// Tests for each:
    /// -# Create double A and B (A>B).  Create timestampA, and timespanB.
    /// -# Verify timestampA + timespanB = A+B
    /// -# Verify timestampA - timespanB = A-B
    /// -# Verify timestampA - timestampB = A-B
    /// -# Repeat above with B>A
    void testAdditionAndSubtraction( void )
    {
        double A = 244332234.224325234;
        double B = 1433453234.2342223;

        Timestamp timestampA( A ), timestampB( B );
        Timespan timespanA( A ), timespanB( B );

        TS_ASSERT_DELTA( Timestamp( A + B ), timestampA + timespanB, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timestamp( A - B ), timestampA - timespanB, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timestamp( B - A ), timestampB - timespanA, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timespan( A - B ), timestampA - timestampB, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timespan( B - A ), timestampB - timestampA, TIMESPAN_FUDGE );
    }

    /// Inequality operator
    ///
    /// Tests:
    /// -# Generate two times A and B with A > B.  Verify A>B true and A<B false.
    void testInquality( void )
    {
        double B = 1033453234.2342223,
               A = 2044332234.224325234;
        Timestamp timestampA( A ), timestampB( B );

        // Make sure the test is predicated on a good assumption
        TS_ASSERT( A > B );

        TS_ASSERT( timestampA > timestampB );
        TS_ASSERT( !( timestampA < timestampB ) );
        TS_ASSERT( timestampA >= timestampB );
        TS_ASSERT( !( timestampA <= timestampB ) );

        timestampB = timestampA;
        TS_ASSERT( timestampA >= timestampB );
        TS_ASSERT( timestampB >= timestampA );


    }

    /// Test the sleepTill function
    void testSleepNow( void )
    {
        const Timespan sleepTimestamp( Timespan::Milliseconds( 250 ) );
        const Timespan ACCEPTABLE_ERROR_SECONDS( 0.01 );

        Timestamp timestampNow = Timestamp::Now(), timestampAfter;

        timestampNow += sleepTimestamp;

        timestampNow.sleepTill();

        timestampAfter = Timestamp::Now();

        TS_ASSERT_DELTA( timestampAfter, timestampNow, ACCEPTABLE_ERROR_SECONDS );
    }

    void testNow( void )
    {
        const Timespan ACCEPTABLE_ERROR_SECONDS( 0.1 );
        Timestamp timestampNow, timestampNowToo;

        timestampNow = Timestamp::Now();
        timestampNowToo = Timestamp::Now();

        TS_ASSERT_DELTA( timestampNow, timestampNowToo, ACCEPTABLE_ERROR_SECONDS );
    }

    /// Test whole units
    void testWholeUnits( void )
    {
        Timestamp timestampOne( 0.0 ), timestampTwo( 3661.001 );

        timestampOne = 0.0;
        timestampOne += Timespan::Hours( 1 );
        timestampOne += Timespan::Minutes( 1 );
        timestampOne += Timespan::Seconds( 1 );
        timestampOne += Timespan::Milliseconds( 1 );

        TS_ASSERT_DELTA( timestampOne, timestampTwo, TIMESPAN_FUDGE );
    }

    void testToString( void )
    {
        Timestamp timestampNow, timestampBefore, timestampAfter;
        time_t rawtime;
        struct tm * timeinfo;

        timestampNow = Timestamp::Now();
        TS_ASSERT( timestampNow > 0.0 );

        rawtime = timestampNow.asTimeT();
        timeinfo = gmtime( &rawtime );

        char bufferNow[80];
        timestampNow.toString( bufferNow, 79 );

        char buffer[80];
        strftime( buffer, 80, "%Y-%m-%dT%H:%M:%S", timeinfo );

        TS_ASSERT_SAME_DATA( bufferNow, buffer, 19 );
    }

};

/**
 *  Unit Tests for class Timespan
 *
 *  \ingroup utils
 */
class Timespan_Test : public CxxTest::TestSuite
{
public:
    // Constructor with no param tests
    /// Tests:
    /// -# Create a new object w/o params.  Expect ZERO_TIMESPAN
    void testConstructorNoArguments( void )
    {
        Timespan thisTimespan;

        TS_ASSERT_EQUALS( thisTimespan.asDouble(), Timespan::ZERO_TIMESPAN.asDouble() );
        TS_ASSERT_EQUALS( thisTimespan, Timespan::ZERO_TIMESPAN );
    }

    // Constructor with double param tests
    /// Tests:
    /// -# Create a new object with double param, Expect asDouble() == param
    void testConstructorDoubleArg( void )
    {
        double param = 100.0;
        Timespan thisTimespan( param );

        TS_ASSERT_EQUALS( thisTimespan.asDouble(), param );
        TS_ASSERT_EQUALS( thisTimespan, Timespan( param ) );
    }

    /// Constructor with copy param tests
    /// -# Create new object copied from another object.  asDouble == asDouble
    /// -# Create new object copied from another object.  Equal with == operator
    void testConstructorCopyArg( void )
    {
        Timespan timespanOne( 100.0 ), timespanTwo( timespanOne );

        TS_ASSERT_EQUALS( timespanOne.asDouble(), timespanTwo.asDouble() );
        TS_ASSERT_EQUALS( timespanOne, timespanTwo );
    }

    /// Copy operator.
    ///
    /// Test:
    /// -# Create two objects with different double params.  Test they are
    ///   different.  Use copy to set one to the other.  Test they are same
    ///   with equality operator.
    void testCopyOperator( void )
    {
        Timestamp timespanOne( 100.0 ), timespanTwo( 1234567890.0 );

        TS_ASSERT_DIFFERS( timespanOne, timespanTwo );
        TS_ASSERT_DIFFERS( timespanOne.asDouble(), timespanTwo.asDouble() );
        timespanOne = timespanTwo;
        TS_ASSERT_EQUALS( timespanOne, timespanTwo );
        TS_ASSERT_EQUALS( timespanOne.asDouble(), timespanTwo.asDouble() );
    }

    /// Set operator
    ///
    /// Test:
    /// -# Create object with double param A.  Test asDouble is not equal to B.
    ///    Use operator to set to B.  Test asDouble is equal to B
    /// -# Create two objects with different double params.  Test they are
    ///   different.  Use copy to set one to the other's asDouble.  Test they are same
    ///   with equality operator.
    void testSetOperatorOne( void )
    {
        double A = 123456.0, B = 2042322342.0;

        Timestamp timespanOne( A );

        TS_ASSERT_DIFFERS( timespanOne.asDouble(), B );
        timespanOne = B;
        TS_ASSERT_EQUALS( timespanOne.asDouble(), B );
    }

    void testSetOperatorTwo( void )
    {
        Timestamp timespanOne( 2042322342.0 ), timespanTwo( 1.0 );

        TS_ASSERT_DIFFERS( timespanOne, timespanTwo );
        timespanOne = timespanTwo.asDouble();
        TS_ASSERT_EQUALS( timespanOne, timespanTwo );
    }

    /// Standard addition/subtraction functions.
    ///
    /// Tests for each:
    /// -# Create double A and B (A>B).  Create timestampA, and timespanB.
    /// -# Verify timespanA + timespanB = A+B
    /// -# Verify timespanA - timespanB = A-B
    /// -# Repeat above with B>A
    void testAdditionAndSubtraction( void )
    {
        double B = 2368.234,
               A = 8242.233345;

        Timespan timespanA( A ), timespanB( B );

        TS_ASSERT_DELTA( Timespan( A + B ), timespanA + timespanB, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timespan( A - B ), timespanA - timespanB, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timespan( B + A ), timespanB + timespanA, TIMESPAN_FUDGE );
        TS_ASSERT_DELTA( Timespan( B - A ), timespanB - timespanA, TIMESPAN_FUDGE );
    }

    /// Inequality operator
    ///
    /// Tests:
    /// -# Generate two times A and B with A > B.  Verify A>B true and A<B false.
    void testInquality( void )
    {
        double B = 2368.234,
               A = 8242.233345;

        Timespan timespanA( A ), timespanB( B );

        // Make sure the test is predicated on a good assumption
        TS_ASSERT( A > B );

        TS_ASSERT( timespanA > timespanB );
        TS_ASSERT( !( timespanA < timespanB ) );
        TS_ASSERT( timespanA >= timespanB );
        TS_ASSERT( !( timespanA <= timespanB ) );

        timespanB = timespanA;
        TS_ASSERT( timespanA >= timespanB );
        TS_ASSERT( timespanB >= timespanA );


    }

    /// Test the sleepFor function
    void testSleepFor( void )
    {
        const Timespan sleepTimestamp( Timespan::Milliseconds( 250 ) );
        const Timespan ACCEPTABLE_ERROR_SECONDS( 0.025 );
        Timestamp timestampBefore, timestampAfter;
        Timespan sleptFor;

        timestampBefore = Timestamp::Now();
        sleepTimestamp.sleepFor();
        timestampAfter = Timestamp::Now();

        sleptFor = timestampAfter - timestampBefore;

        TS_ASSERT_DELTA( sleepTimestamp, sleptFor, ACCEPTABLE_ERROR_SECONDS );
    }


    void testToString( void )
    {
        Timespan span = 1234.5678;

        char buffer[80];
        span.toString( buffer, 79 );

        TS_ASSERT( strstr( buffer, "1234.5678" ) );
    }

};



#endif // _TC_TIME_H
