/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include <gtest/gtest.h>
#include <mrpt/config.h>
#include <mrpt/core/Clock.h>

#include <chrono>
#include <thread>

namespace
{
void test_delay()
{
  const double t0 = mrpt::Clock::nowDouble();
  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  const double t1 = mrpt::Clock::nowDouble();

  EXPECT_GT(t1 - t0, 0.008);  // ideally, near 0.010
  EXPECT_LT(t1 - t0, 5.0);    // just detect it's not a crazy number
}
}  // namespace

TEST(clock, delay_Realtime)
{
  // Default:
  test_delay();

  // Monotonic:
  mrpt::Clock::setActiveClock(mrpt::Clock::Source::Monotonic);
  test_delay();

#if 0  // non-repetitive results
	// mono->rt offset:
	const uint64_t d1 = mrpt::Clock::getMonotonicToRealtimeOffset();
	std::this_thread::sleep_for(std::chrono::milliseconds(50));
	mrpt::Clock::resetMonotonicToRealTimeEpoch();
	const uint64_t d2 = mrpt::Clock::getMonotonicToRealtimeOffset();
	EXPECT_GT(d1, d2);
#endif

  // Realtime:
  mrpt::Clock::setActiveClock(mrpt::Clock::Source::Realtime);
  test_delay();
}

TEST(clock, changeSource)
{
  const double t0 = mrpt::Clock::nowDouble();
  mrpt::Clock::setActiveClock(mrpt::Clock::Source::Monotonic);

  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  const double t1 = mrpt::Clock::nowDouble();
  mrpt::Clock::setActiveClock(mrpt::Clock::Source::Realtime);

  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  const double t2 = mrpt::Clock::nowDouble();

  EXPECT_GT(t1 - t0, 0.008);  // ideally, near 0.010
  EXPECT_LT(t1 - t0, 5.0);    // just detect it's not a crazy number

  EXPECT_GT(t2 - t1, 0.008);  // ideally, near 0.010
  EXPECT_LT(t2 - t1, 5.0);    // just detect it's not a crazy number
}

TEST(clock, checkSynchEpoch)
{
  for (int i = 0; i < 20; i++)
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(5));
    const int64_t err = mrpt::Clock::resetMonotonicToRealTimeEpoch();

    // it should be a really small number in a regular computer,
    // but we set the threshold much higher due to spurious errors
    // when running unit tests in VMs (build farms)
#if MRPT_IN_EMSCRIPTEN
    const int64_t errLimit = 1000 * 1000;  // We are running on Javascript!
#else
    // normally much smaller, but for busy build servers
    const int64_t errLimit = 90 * 1000;
#endif

    EXPECT_LT(std::abs(err), errLimit);
  }
}

TEST(clock, simulatedTime)
{
  const auto t0 = mrpt::Clock::now();
  const auto prevSrc = mrpt::Clock::getActiveClock();
  // Enable simulated time:
  mrpt::Clock::setActiveClock(mrpt::Clock::Simulated);

  // Check that time is stopped:
  mrpt::Clock::setSimulatedTime(t0);
  const auto t1 = mrpt::Clock::now();
  EXPECT_EQ(t0, t1);
  std::this_thread::sleep_for(std::chrono::milliseconds(10));
  const auto t2 = mrpt::Clock::now();
  EXPECT_EQ(t0, t2);

  // Check time moving forward:
  auto tset2 = t0 + std::chrono::milliseconds(1000);
  mrpt::Clock::setSimulatedTime(tset2);
  const auto t3 = mrpt::Clock::now();
  EXPECT_EQ(tset2, t3);

  // Restore
  mrpt::Clock::setActiveClock(prevSrc);
}
