/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, 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);
}
