#include "toolbox.hpp"

inline static constexpr uint64_t u64(const double& n) noexcept { return static_cast<uint64_t>(n); }

static const constexpr size_t N_TIMES = 11;
static const constexpr std::array<const char*, N_TIMES> time_formats = { "ns", "us", "ms", "s", "m", "h", "j", "w", "M", "y", "c" };
static const constexpr std::array<uint64_t, N_TIMES> time_numbers = { 1, u64(1e3), u64(1e6), u64(1e9), u64(6e10), u64(36e11), u64(864e11),
								u64(6048e11), u64(26784e11), u64(31536e12), u64(31536e14) };

/**
 * @brief Format the time in seconds in human readable format.
 *
 * @param time number of seconds
 * @return The formatted human readable string.
 */
std::string format_time(uint64_t time) noexcept {
	if (time == 0)
		return "0s";

	std::string s = "";
	uint64_t res;
	for (int32_t i = N_TIMES - 1; i >= 3; --i) {
		const uint64_t time_number = time_numbers[i] / u64(1e9); // Converting nanosecond timestamp to second
		if (time >= time_number) {
			res = time / time_number;
			time %= time_number;
			s += std::to_string(res) + time_formats[i] + " ";
		}
	}

	// Remove trailing character
	s.pop_back();

	return s;
}

/**
 * @brief Format the time in nanoseconds in human readable format.
 *
 * @param time Time in nanoseconds
 * @return std::string The formatted human readable string.
 */
std::string format_time_ns(uint64_t time) noexcept {
	if (time == 0)
		return "0ns";

	std::string s = "";
	uint64_t res;
	for (int32_t i = N_TIMES - 1; i >= 0; --i) {
		if (time >= time_numbers[i]) {
			res = time / time_numbers[i];
			time %= time_numbers[i];
			s += std::to_string(res) + time_formats[i] + " ";
		}
	}

	// Remove trailing character
	s.pop_back();

	return s;
}

static const constexpr size_t N_BYTES = 7;
static const constexpr std::array<const char*, N_BYTES> bytes_formats = { "", "K", "M", "G", "T", "P", "E", }; //"Z", "Y" };
static const constexpr uint64_t total_bytes = u64(1)<<(10 * (N_BYTES - 1));

/**
 * @brief Convert the number of byte in JEDEC standard form.
 * See more : https://en.wikipedia.org/wiki/JEDEC_memory_standards
 *
 * @param bytes Number of bytes
 * @return JEDEC compliant formatted number of bytes
 */
std::string format_byte_size(uint64_t bytes) noexcept {
	if (bytes == 0)
		return "0B";
	uint64_t prod = total_bytes;

	std::string s = "";
	uint64_t res;
	for (size_t i = N_BYTES; i > 0; --i) {
		if (bytes >= prod) {
			res = bytes / prod;
			bytes %= prod;
			s += std::to_string(res) + bytes_formats[i - 1] + "B ";
		}
		prod /= u64(1)<<10; // 1024
	}

	// Remove trailing character
	s.pop_back();

	return s;
}

/**
 * @brief Format a number with a separator (i.e. 1000 as 1,000)
 *
 * @param k number to format
 * @param separator used between each thouand
 * @return Formatted number
 */
std::string thousand_sep(uint64_t k, const char& separator) noexcept {
	const std::string n = std::to_string(k);
	const uint64_t st_size = n.length() + (n.length() - 1) / 3;
	std::string s = std::string(st_size, ' ');

	int8_t c = 0, n_i = n.length() - 1, j = st_size - 1;
	for(; n_i >= 0; --n_i) {
		s[j--] = n[n_i];
		if (++c == 3 && j >= 0) {
			s[j--] = separator;
			c = 0;
		}
	}

	return s;

	//uint64_t len = n.length(), dlen = 3;

	//while (len > dlen) {
	//	n.insert(len - dlen, 1, separator);
	//	dlen += 4;
	//	len += 1;
	//}
	//return n;
}