/*
 * This file is part of the Owl Positioning System (OwlPS) project.
 * It is subject to the copyright notice and license terms in the
 * COPYRIGHT.t2t file found in the top-level directory of this
 * distribution and at
 *   https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t
 * No part of the OwlPS Project, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms
 * contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be
 * distributed along with this file, either separately or by replacing
 * this notice by the COPYRIGHT.t2t file's contents.
 */

#include <cxxtest/TestSuite.h>

#include "timestamp.hh"


// Allows to test equality of two struct timespec
bool operator==(const struct timespec &d1,
                const struct timespec &d2)
{
  return
    d1.tv_sec == d2.tv_sec &&
    d1.tv_nsec == d2.tv_nsec ;
}


class Timestamp_test: public CxxTest::TestSuite
{
public:

  void test_zero(void)
  {
    // Default constructor
    Timestamp timestamp1 ;

    // struct timespec constructor
    struct timespec timespec1 ;
    timespec1.tv_sec = 0 ;
    timespec1.tv_nsec = 0 ;
    Timestamp timestamp2(timespec1) ;
    TS_ASSERT_EQUALS(timestamp1, timestamp2) ;

    // ms constructor
    uint64_t msec = 0 ; // ms time value
    Timestamp timestamp3(msec) ;
    TS_ASSERT_EQUALS(timestamp1, timestamp3) ;

    // Copy constructor
    Timestamp timestamp4(timestamp1) ;
    TS_ASSERT_EQUALS(timestamp1, timestamp4) ;

    // Equality
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp1),
                     timespec1) ;
    TS_ASSERT_EQUALS(timestamp1, timespec1) ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp1), msec) ;
    TS_ASSERT_EQUALS(timestamp1, msec) ;
  }

  void test_non_zero(void)
  {
    uint64_t sec = 1234567 ;
    uint64_t nsec = 891234567 ; // Don't put a value >= 1 sec. for the test!
    uint64_t msec = sec * 1000 + nsec / 1000000 ;
    struct timespec timespec1 ;
    timespec1.tv_sec = sec ;
    timespec1.tv_nsec = nsec ;

    // struct timespec constructor
    Timestamp timestamp1(timespec1) ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp1),
                     timespec1) ;
    TS_ASSERT_EQUALS(timestamp1, timespec1) ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp1), msec) ;
    TS_ASSERT_EQUALS(timestamp1, msec) ;

    // ms constructor (deprecated)
    Timestamp timestamp2(msec) ;
    timespec1.tv_nsec = nsec / 1000000 * 1000000 ; // Round to ms
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp2),
                     timespec1) ;
    TS_ASSERT_EQUALS(timestamp2, timespec1) ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp2), msec) ;
    TS_ASSERT_EQUALS(timestamp2, msec) ;

    // ms and ns give different values
    TS_ASSERT_DIFFERS(timestamp1, timestamp2) ;

    // Copy constructor
    Timestamp timestamp3(timestamp1) ;
    TS_ASSERT_EQUALS(timestamp1, timestamp3) ;
  }

  void test_current_time(void)
  {
    // Note: we could test update_current_time() in replay mode.

    Timestamp timestamp1 ;
    struct timespec current_time ;

    TS_ASSERT(timestamp1.now()) ;
    if (clock_gettime(CLOCK_REALTIME, &current_time))
      {
        TS_FAIL("Error getting current time!") ;
        return ;
      }
    const Timestamp &now = Timestamp::get_current_time() ;
    Timestamp timestamp2(current_time) ;
    TS_ASSERT_DELTA(static_cast<uint64_t>(timestamp1),
                    static_cast<uint64_t>(timestamp2),
                    100) ;
    TS_ASSERT_DELTA(static_cast<uint64_t>(timestamp2),
                    static_cast<uint64_t>(now),
                    100) ;

    TS_ASSERT_DIFFERS(static_cast<struct timespec>(timestamp1),
                      current_time) ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp2),
                     current_time) ;

    // elapsed()
    Timestamp elapsed11(timestamp1.elapsed(timestamp1)) ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(elapsed11), 0) ;
    TS_ASSERT(! elapsed11) ;
    Timestamp elapsed12(timestamp1.elapsed(timestamp2)) ;
    TS_ASSERT(static_cast<uint64_t>(elapsed12) < 100) ;
    Timestamp elapsed21(timestamp2.elapsed(timestamp1)) ;
    TS_ASSERT_EQUALS(elapsed12, elapsed21) ;
  }

  void test_affectation(void)
  {
    struct timespec current_time ;
    if (clock_gettime(CLOCK_REALTIME, &current_time))
      {
        TS_FAIL("Error calling clock_gettime()!") ;
        return ;
      }

    // struct timespec constructor
    Timestamp timestamp1(current_time) ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp1),
                     current_time) ;

    // ms constructor (deprecated)
    current_time.tv_nsec = current_time.tv_nsec / 1000000 * 1000000 ;
    uint64_t msec = timestamp1 ;
    Timestamp timestamp2(msec) ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp2), msec) ;
    TS_ASSERT_EQUALS(timestamp2, msec) ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp2),
                     current_time) ;
    TS_ASSERT_EQUALS(timestamp2, current_time) ;

    // Affectation
    ++current_time.tv_sec ;
    timestamp2 = current_time ;
    TS_ASSERT_DIFFERS(timestamp1, timestamp2) ;
    msec = timestamp2 ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp2), msec) ;
    TS_ASSERT_EQUALS(timestamp2, msec) ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp2),
                     current_time) ;
    TS_ASSERT_EQUALS(timestamp2, current_time) ;

    msec = 1234567891234ull ;
    timestamp2 = msec ;
    TS_ASSERT_EQUALS(static_cast<uint64_t>(timestamp2), msec) ;
    TS_ASSERT_EQUALS(timestamp2, msec) ;
    current_time = timestamp2 ;
    TS_ASSERT_EQUALS(static_cast<struct timespec>(timestamp2),
                     current_time) ;
    TS_ASSERT_EQUALS(timestamp2, current_time) ;
  }

  void test_comparison(void)
  {
    Timestamp today ;
    today.now() ;
    Timestamp today2(today) ;
    struct timespec today_timespec = today ;
    uint64_t today_int = today ;

    uint64_t yesterday_int = today_int - 3600*24 ;
    Timestamp yesterday(yesterday_int) ;
    struct timespec yesterday_timespec = yesterday ;

    TS_ASSERT_EQUALS(today, today2) ;
    TS_ASSERT_LESS_THAN_EQUALS(today, today2) ;
    TS_ASSERT(today >= today2) ;
    TS_ASSERT(! (today > today2)) ;
    TS_ASSERT(! (today < today2)) ;
    TS_ASSERT_EQUALS(today, today_int) ;
    TS_ASSERT_LESS_THAN_EQUALS(today, today_int) ;
    TS_ASSERT(today >= today_int) ;
    TS_ASSERT(! (today > today_int)) ;
    TS_ASSERT(! (today < today_int)) ;
    TS_ASSERT_EQUALS(today, today_timespec) ;
    TS_ASSERT_LESS_THAN_EQUALS(today, today_timespec) ;
    TS_ASSERT(today >= today_timespec) ;
    TS_ASSERT(! (today > today_timespec)) ;
    TS_ASSERT(! (today < today_timespec)) ;

    TS_ASSERT_DIFFERS(today, yesterday) ;
    TS_ASSERT_LESS_THAN(yesterday, today) ;
    TS_ASSERT_LESS_THAN_EQUALS(yesterday, today) ;
    TS_ASSERT(! (yesterday > today)) ;
    TS_ASSERT(! (yesterday >= today)) ;
    TS_ASSERT_DIFFERS(today, yesterday_int) ;
    TS_ASSERT_LESS_THAN(yesterday, today_int) ;
    TS_ASSERT_LESS_THAN_EQUALS(yesterday, today_int) ;
    TS_ASSERT(! (yesterday > today_int)) ;
    TS_ASSERT(! (yesterday >= today_int)) ;
    TS_ASSERT_DIFFERS(today, yesterday_timespec) ;
    TS_ASSERT_LESS_THAN(yesterday, today_timespec) ;
    TS_ASSERT_LESS_THAN_EQUALS(yesterday, today_timespec) ;
    TS_ASSERT(! (yesterday > today_timespec)) ;
    TS_ASSERT(! (yesterday >= today_timespec)) ;
  }

  void test_clear(void)
  {
    uint64_t zero = 0 ;
    struct timespec zero_timespec ;
    zero_timespec.tv_sec = 0 ;
    zero_timespec.tv_nsec = 0 ;
    Timestamp zero_timestamp ;

    Timestamp today ;
    today.now() ;

    TS_ASSERT_DIFFERS(today, zero) ;
    TS_ASSERT_DIFFERS(today, zero_timespec) ;
    TS_ASSERT_DIFFERS(today, zero_timestamp) ;

    today.clear() ;
    TS_ASSERT_EQUALS(today, zero) ;
    TS_ASSERT_EQUALS(today, zero_timespec) ;
    TS_ASSERT_EQUALS(today, zero_timestamp) ;
  }

} ;
