/*
 * Copyright © 2019-2026 Pierrick Caillon <pierrick.caillon+zipdirfs@megami.fr>
 */
#include "gtest.h"
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <random>
#include <string>
#include <unistd.h>
#include <vector>

namespace
{
	unsigned init();
	unsigned gtest_rand_init = []()
	{
		unsigned seed = std::time(nullptr);
		std::srand(seed);
		return seed;
	}();
} // namespace

namespace Test
{
	Generator generator;
	namespace
	{
		std::default_random_engine randgen((std::random_device())());
		std::uniform_real_distribution<double> distribution;
	} // namespace
	double Generator::next() { return distribution(randgen); }
	long double rand(const long double min, const long double max)
	{
		return min
			+ Generator::next()
			* std::nextafter(max - min, std::numeric_limits<long double>::max());
	}
	std::uintmax_t rand(const std::uintmax_t min, const std::uintmax_t max)
	{
		const auto limit = std::numeric_limits<std::uintmax_t>::max();
		const auto size = max - min;
		return min + Generator::next() * (size == limit ? limit : (size + 1));
	}
	std::intmax_t rand(const std::intmax_t min, const std::intmax_t max)
	{
		const auto limit = std::numeric_limits<std::intmax_t>::max();
		const auto size = max - min;
		return min + Generator::next() * (size == limit ? limit : (size + 1));
	}

	static auto clock_anchor()
	{
		using namespace std::chrono;
		static const auto anchor = []
		{
			while (true)
			{
				const auto sysBefore = system_clock::now();
				const auto fsRawNow = std::filesystem::file_time_type::clock::now();
				const auto sysAfter = system_clock::now();

				const auto sysBeforeRoundedSeconds = floor<seconds>(sysBefore);
				const auto sysAfterRoundedSeconds = floor<seconds>(sysAfter);
				const auto fsRoundedSeconds = floor<seconds>(fsRawNow);

				if (sysBeforeRoundedSeconds == sysAfterRoundedSeconds)
				{
					return std::pair{
						std::filesystem::file_time_type{fsRoundedSeconds.time_since_epoch()},
						system_clock::time_point{sysBeforeRoundedSeconds.time_since_epoch()}};
				}
			}
		}();
		return anchor;
	}

	std::filesystem::file_time_type to_file_time(const std::time_t value)
	{
		using namespace std::chrono;
		auto [fsNow, sysNow] = clock_anchor();
        const auto sysTime = system_clock::from_time_t(value);
		return time_point_cast<std::filesystem::file_time_type::duration>(sysTime - sysNow + fsNow);
	}

	std::time_t to_time_t(const std::filesystem::file_time_type& value)
	{
		using namespace std::chrono;
		auto [fsNow, sysNow] = clock_anchor();
		const auto sctp = time_point_cast<system_clock::duration>(value - fsNow + sysNow);
		return system_clock::to_time_t(sctp);
	}

	std::filesystem::path tempFolderPath()
	{
		auto pattern = (std::filesystem::temp_directory_path() / "test-XXXXXX").string();
		std::vector<char> buffer(pattern.begin(), pattern.end());
		buffer.push_back('\0');
		auto path = ::mkdtemp(buffer.data());
		if (path == nullptr)
		{
			throw std::runtime_error("Failed to create temporary folder");
		}
		return {path};
	}

	std::filesystem::path tempFilePath()
	{
		auto pattern = (std::filesystem::temp_directory_path() / "test-XXXXXX").string();
		std::vector<char> buffer(pattern.begin(), pattern.end());
		buffer.push_back('\0');
		if (const auto fd = ::mkstemp(buffer.data()); fd >= 0)
		{
			::close(fd);
		}
		return {buffer.data()};
	}

	std::filesystem::file_time_type randomFileTime(std::optional<std::filesystem::file_time_type> max_time)
	{
		using file_time_type = std::filesystem::file_time_type;
		using rep = file_time_type::duration::rep;
		rep max{UINT32_MAX};
		if (max_time.has_value())
		{
			max = to_time_t(max_time.value());
		}
		return to_file_time(rand(UINT16_MAX, max));
	}

	std::filesystem::file_time_type randomFileTimeDifferentFrom(
		const std::filesystem::file_time_type& t)
	{
		std::filesystem::file_time_type result;
		do
		{
			result = randomFileTime();
		} while (result == t);
		return result;
	}
} // namespace Test

