//
// LDASTools AL - A library collection to provide an abstraction layer
//
// Copyright (C) 2018 California Institute of Technology
//
// LDASTools AL is free software; you may redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 (GPLv2) of the
// License or at your discretion, any later version.
//
// LDASTools AL is distributed in the hope that it will be useful, but
// without any warranty or even the implied warranty of merchantability
// or fitness for a particular purpose. See the GNU General Public
// License (GPLv2) for more details.
//
// Neither the names of the California Institute of Technology (Caltech),
// The Massachusetts Institute of Technology (M.I.T), The Laser
// Interferometer Gravitational-Wave Observatory (LIGO), nor the names
// of its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// You should have received a copy of the licensing terms for this
// software included in the file LICENSE located in the top-level
// directory of this package. If you did not, you can view a copy at
// http://dcc.ligo.org/M1500244/LICENSE
//

#include <time.h>

#include <sstream>

#include "ldastoolsal/unittest.h"
#include "ldastoolsal/gpstime.hh"

using LDASTools::AL::GPSTime;
using namespace std;

LDASTools::Testing::UnitTest Test;
using LDASTools::AL::JournalLog;

void
validate_leap_seconds( const char* Date, INT_2U Seconds, INT_4U GPSSeconds )
{
    time_t    now;
    struct tm cur;
    char      time_buf[ 32 ];

    time( &now );
    gmtime_r( &now, &cur );
    cur.tm_sec = 0;
    cur.tm_min = 0;
    cur.tm_hour = 0;
    strptime( Date, "%Y %b %d", &cur );
    now = mktime( &cur );
    now -= timezone;
    gmtime_r( &now, &cur );
    asctime_r( &cur, time_buf );
    LDASTools::AL::GPSTime gps( now, 0, LDASTools::AL::GPSTime::UTC );
    LDASTools::AL::GPSTime gps_sec( GPSSeconds, 0 );
    Test.Check( gps == gps_sec )
        << "Validating UTC conversion: UTC Date: " << Date
        << " (difference: " << ( gps_sec - gps ) << ")" << std::endl;
    gps -= 1.0;
    Test.Check( gps.GetLeapSeconds( ) == ( Seconds - 1 ) )
        << "Validating leap seconds for (-1): " << Date << " ("
        << gps.GetLeapSeconds( ) << " =?= " << ( Seconds - 1 ) << ")"
        << std::endl;
    gps += 1.0;
    Test.Check( gps.GetLeapSeconds( ) == Seconds )
        << "Validating leap seconds for ( 0): " << Date << " ("
        << gps.GetLeapSeconds( ) << " =?= " << Seconds << ")" << std::endl;
    gps += 1.0;
    Test.Check( gps.GetLeapSeconds( ) == Seconds )
        << "Validating leap seconds for (+1): " << Date << " ("
        << gps.GetLeapSeconds( ) << " =?= " << Seconds << ")" << std::endl;
}

int
main( int argc, char** argv )
{
    Test.Init( argc, argv );
    Test.Message( )
        << "$Id: tGPSTime.cc,v 1.12 2009/12/17 18:19:45 emaros Exp $\n";

    std::ofstream null_stream( "/dev/null" );

    JournalLog( null_stream );

    try
    {
        {
            GPSTime t;
            Test.Check( t.GetSeconds( ) == 0, "GPSTime().GetSeconds() == 0" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "GPSTime().GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 2 );
            Test.Check( t.GetSeconds( ) == 1,
                        "GPSTime(1,2).GetSeconds() == 1" );
            Test.Check( t.GetNanoseconds( ) == 2,
                        "GPSTime(1,2).GetNanoseconds() == 2" );
            Test.Check( t.GPSNanoseconds( ) ==
                            GPSTime::gpsnanoseconds_type( 1000000002 ),
                        "GPSNanoseconds( ) == 1000000002" );
        }

        try
        {
            GPSTime t( 0, 1000000000 );
            GPSTime t2( 1, 0 );

            Test.Check( t == t2 )
                << "Normalization of GPSTime( 0, 1000000000 )" << std::endl;
        }
        catch ( const std::out_of_range& x )
        {
            Test.Check( true,
                        "GPSTime(0, 1000000000) throws std::out_of_range" );
        }
        catch ( ... )
        {
            Test.Check( false,
                        "GPSTime(0, 1000000000) throws std::out_of_range "
                        "(wrong throw)" );
        }

        {
            GPSTime t( GPSTime( 1, 2 ) );
            Test.Check( t.GetSeconds( ) == 1,
                        "GPSTime(GPSTime(1,2)).GetSeconds() == 1" );
            Test.Check( t.GetNanoseconds( ) == 2,
                        "GPSTime(GPSTime(1,2)).GetNanoseconds() == 2" );
        }

        {
            GPSTime t;
            t = GPSTime( 1, 2 );
            Test.Check( t.GetSeconds( ) == 1,
                        "(t = GPSTime(1,2)).GetSeconds() == 1" );
            Test.Check( t.GetNanoseconds( ) == 2,
                        "(t = GPSTime(1,2)).GetNanoseconds() == 2" );
        }

        {
            GPSTime t( 1, 200 );
            t += 1.25;
            Test.Check( t.GetSeconds( ) == 2,
                        "(GPSTime(1,2) += 1.2).GetSeconds() == 2" );
            // Test.Message() << "ns=" << t.GetNanoseconds() << '\n';
            Test.Check( t.GetNanoseconds( ) == 250000200,
                        "(GPSTime(1,2) += 1.2).GetNanoseconds() == 250000200" );
        }

        {
            GPSTime t( 1, 500000000 );
            t += 1.5;
            Test.Check( t.GetSeconds( ) == 3,
                        "(GPSTime(1, 500000000) += 1.5).GetSeconds() == 3" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(GPSTime(1,500000000) += 1.5).GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 500000000 );
            t += -1.5;
            Test.Check( t.GetSeconds( ) == 0,
                        "(GPSTime(1, 500000000) += 1.5).GetSeconds() == 0" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(GPSTime(1,500000000) += 1.5).GetNanoseconds() == 0" );
        }

        try
        {
            GPSTime t( 1, 500000000 );
            t += 1e12;
            Test.Check( false,
                        "GPSTime(1, 500000000) += 1e12 throws "
                        "std::out_of_range (no throw)" );
        }
        catch ( const std::out_of_range& x )
        {
            Test.Check(
                true,
                "GPSTime(1, 500000000) += 1e12 throws std::out_of_range" );
        }
        catch ( ... )
        {
            Test.Check( false,
                        "GPSTime(1, 500000000) += 1e12 throws "
                        "std::out_of_range (wrong throw)" );
        }

        try
        {
            GPSTime t( 1, 500000000 );
            t += -2;
            Test.Check( false,
                        "GPSTime(1, 500000000) += -2 throws std::out_of_range "
                        "(no throw)" );
        }
        catch ( const std::out_of_range& x )
        {
            Test.Check(
                true, "GPSTime(1, 500000000) += -2 throws std::out_of_range" );
        }
        catch ( ... )
        {
            Test.Check( false,
                        "GPSTime(1, 500000000) += -2 throws std::out_of_range "
                        "(wrong throw)" );
        }

        {
            GPSTime t( 1, 500000000 );
            t -= 1.5;
            Test.Check( t.GetSeconds( ) == 0,
                        "(GPSTime(1, 500000000) -= 1.5).GetSeconds() == 0" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(GPSTime(1,500000000) -= 1.5).GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 500000000 );
            t = t + 1.5;
            Test.Check( t.GetSeconds( ) == 3,
                        "(GPSTime(1, 500000000) + 1.5).GetSeconds() == 3" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(GPSTime(1, 500000000) + 1.5).GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 500000000 );
            t = 1.5 + t;
            Test.Check( t.GetSeconds( ) == 3,
                        "(1.5 + GPSTime(1, 500000000)).GetSeconds() == 3" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(1.5 + GPSTime(1, 500000000)).GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 500000000 );
            t = t - 1.5;
            Test.Check( t.GetSeconds( ) == 0,
                        "(GPSTime(1, 500000000) - 1.5).GetSeconds() == 0" );
            Test.Check( t.GetNanoseconds( ) == 0,
                        "(GPSTime(1, 500000000) - 1.5).GetNanoseconds() == 0" );
        }

        {
            GPSTime t( 1, 500000000 );
            double  d = t - t;
            Test.Check(
                d == 0.0,
                "(GPSTime(1, 500000000) - GPSTime(1, 500000000)) == 0.0" );
        }

        {
            GPSTime s( 1, 2 );
            GPSTime t( 1, 3 );
            GPSTime u( 2, 1 );

#define TEST_CHECK_ECHO( ARGUMENT ) Test.Check( ( ARGUMENT ), #ARGUMENT )

            TEST_CHECK_ECHO( s == s );
            TEST_CHECK_ECHO( !( s == t ) );

            TEST_CHECK_ECHO( !( s != s ) );
            TEST_CHECK_ECHO( s != t );

            TEST_CHECK_ECHO( !( s < s ) );
            TEST_CHECK_ECHO( s < t );
            TEST_CHECK_ECHO( s < u );
            TEST_CHECK_ECHO( !( t < s ) );
            TEST_CHECK_ECHO( t < u );
            TEST_CHECK_ECHO( !( u < s ) );
            TEST_CHECK_ECHO( !( u < t ) );

            TEST_CHECK_ECHO( !( s > s ) );
            TEST_CHECK_ECHO( !( s > t ) );
            TEST_CHECK_ECHO( !( s > u ) );
            TEST_CHECK_ECHO( t > s );
            TEST_CHECK_ECHO( !( t > u ) );
            TEST_CHECK_ECHO( u > s );
            TEST_CHECK_ECHO( u > t );

            TEST_CHECK_ECHO( s <= s );
            TEST_CHECK_ECHO( s <= t );
            TEST_CHECK_ECHO( s <= u );
            TEST_CHECK_ECHO( !( t <= s ) );
            TEST_CHECK_ECHO( t <= u );
            TEST_CHECK_ECHO( !( u <= s ) );
            TEST_CHECK_ECHO( !( u <= t ) );

            TEST_CHECK_ECHO( s >= s );
            TEST_CHECK_ECHO( !( s >= t ) );
            TEST_CHECK_ECHO( !( s >= u ) );
            TEST_CHECK_ECHO( t >= s );
            TEST_CHECK_ECHO( !( t >= u ) );
            TEST_CHECK_ECHO( u >= s );
            TEST_CHECK_ECHO( u >= t );

#undef TEST_CHECK_ECHO
        }

        {
            // Test for Now() function
            const GPSTime now1 = GPSTime::NowGPSTime( );

            GPSTime now2;
            now2.Now( );

            Test.Message( ) << "Now() == " << now1 << endl;

            Test.Check( now1.GetSeconds( ) == now2.GetSeconds( ) )
                << "Now() seconds are correct" << endl;

            Test.Check( now1.GetNanoseconds( ) <= now2.GetNanoseconds( ) )
                << "Now() nanoseconds are correct" << endl;
        }
        //---------------------------------------------------------------
        // Test for calculating the the leap seconds
        //    ( JD( N ) - JD( N - 1) * 24 * 3600 ) + GPS( N - 1 )
        //        + ( LEAP_SECONDS(N) - LEAP_SECONDS(N-1))
        //---------------------------------------------------------------
        validate_leap_seconds( "1981 JUL  1", 20, INT_4U( 46828801 ) );
        validate_leap_seconds( "1982 JUL  1", 21, INT_4U( 78364802 ) );
        validate_leap_seconds( "1983 JUL  1", 22, INT_4U( 109900803 ) );
        validate_leap_seconds( "1985 JUL  1", 23, INT_4U( 173059204 ) );
        validate_leap_seconds( "1988 JAN  1", 24, INT_4U( 252028805 ) );
        validate_leap_seconds( "1990 JAN  1", 25, INT_4U( 315187206 ) );
        validate_leap_seconds( "1991 JAN  1", 26, INT_4U( 346723207 ) );
        validate_leap_seconds( "1992 JUL  1", 27, INT_4U( 393984008 ) );
        validate_leap_seconds( "1993 JUL  1", 28, INT_4U( 425520009 ) );
        validate_leap_seconds( "1994 JUL  1", 29, INT_4U( 457056010 ) );
        validate_leap_seconds( "1996 JAN  1", 30, INT_4U( 504489611 ) );
        validate_leap_seconds( "1997 JUL  1", 31, INT_4U( 551750412 ) );
        validate_leap_seconds( "1999 JAN  1", 32, INT_4U( 599184013 ) );
        validate_leap_seconds( "2006 JAN  1", 33, INT_4U( 820108814 ) );
        validate_leap_seconds( "2009 JAN  1", 34, INT_4U( 914803215 ) );
        validate_leap_seconds( "2012 JUL  1", 35, INT_4U( 1025136016 ) );
        validate_leap_seconds( "2015 JUL  1", 36, INT_4U( 1119744017 ) );
        validate_leap_seconds( "2017 JAN  1", 37, INT_4U( 1167264018 ) );
    }
    catch ( const std::exception& x )
    {
        Test.Check( false, x.what( ) );
    }
    catch ( ... )
    {
        Test.Check( false, "unknown exception" );
    }
    JournalLog.IsOpen( false );
    null_stream.close( );
    Test.Exit( );
}
