#include <CppUTest/CommandLineTestRunner.h>
#include <CppUTest/TestHarness.h>
#include <iostream>

// The functions contained in our bsp_time.cpp fake, are declared in system_time.hpp
// At compile time, the compiler link the implementation of those functions that are
// defined in our CPF/Tests/mocks/bsp_time.cpp, because of how the CMake build is setup.
#include <system_time.hpp>

// C++ Libraries used in these tests
#include <chrono>
#include <thread>

TEST_GROUP(BSPTimeFake){};

TEST(BSPTimeFake, FirstTest)
{
	// I always write this test first and run it to confirm my test is being built and run
	// correctly.
	CHECK_TRUE_TEXT(true, "Test Fail! Make this pass!");
}

// Test names should be descriptive
TEST(BSPTimeFake, uptime_increments)
{
	using namespace std::this_thread; // sleep_for, sleep_until
	using namespace std::chrono_literals; // ns, us, ms, s, h, etc.
	using std::chrono::system_clock;

	uint64_t tic = systime::getUptimeMilliseconds();

	// NOTE: This call gets passed to the operating systems kernel. During my testing on Windows,
	// realistic sleep times were 2-8x the intended duration likely due to operating system
	// inefficiencies. Thus, as long as the total sleep time is > 2ms, we will call it a success
	sleep_for(2ms);
	uint64_t toc = systime::getUptimeMilliseconds();

	bool slept_sufficiently_long = (toc - tic) >= 2;
	CHECK(slept_sufficiently_long);
}

TEST(BSPTimeFake, delay)
{
	// There is no gaurenteed test execution order, so you cannot rely upon the success of the above
	// tests. e.g. if I get the above test passing, I wouldn't want to utilize the
	// systime::getUptimeMilliseconds() below.

	using namespace std::chrono;
	auto timepoint = system_clock::now();

	auto tic = duration_cast<milliseconds>(timepoint.time_since_epoch()).count();

	// If properly implemented, this should have a 2ms delay
	systime::delay(2);

	// grab the timepoint again, and then compute the delta
	timepoint = system_clock::now();
	auto toc = duration_cast<milliseconds>(timepoint.time_since_epoch()).count();

	bool slept_sufficiently_long = (toc - tic) >= 2;
	CHECK(slept_sufficiently_long);
}

TEST(BSPTimeFake, getDateTimeNow)
{
	using namespace std::this_thread; // sleep_for, sleep_until
	using namespace std::chrono_literals; // ns, us, ms, s, h, etc.
	using std::chrono::system_clock;

	systime::DateTimeStruct tic = systime::getDateTimeNow();
	sleep_for(2ms);
	systime::DateTimeStruct toc = systime::getDateTimeNow();

	// This function is tested elsewhere, so safe to trust it.
	auto dt = systime::getDuration_ms(toc, tic);

	bool slept_sufficiently_long = dt >= 2;
	CHECK(slept_sufficiently_long);
}

TEST(BSPTimeFake, yearRollover)
{
	// Watch for previous bug in mocked systime::getDateTimeNow() that resulted from
	// dt.year = static_cast<uint8_t>(now->tm_year + 1900);
	// Since that would limit dt.year to 0-255

	systime::DateTimeStruct t = systime::getDateTimeNow();

	bool noYearRollover = (t.year > 256);
	CHECK(noYearRollover);
}
