#include "toolbox.hpp"
#include <iostream>
#include <assert.h>

/**
 * @brief Test if a given result is equal of the expected one and log result
 *
 * @tparam T type of returning values
 * @param name of the unit test
 * @param expected result of the function call
 * @param result of the function
 */
template<typename T>
static void Assert(const char* const name, const T& expected, const T& result) noexcept {
	if(expected != result){
		std::cerr << "For test named " << name << " Expected '" << expected << "' but got '" << result << "' instead\n";
		assert(false);
	}
}

/**
 * @brief Test suite for the format_byte_size output
 */
void format_byte_size_test(void) noexcept {
	Assert("format_byte_size null",      std::string("0B"),                                            format_byte_size(static_cast<uint64_t>(0)));
	Assert("format_byte_size byte",      std::string("1B"),                                            format_byte_size(static_cast<uint64_t>(1)));
	Assert("format_byte_size kilobyte",  std::string("1KB"),                                           format_byte_size(static_cast<uint64_t>(1)<<10));
	Assert("format_byte_size megabyte",  std::string("1MB"),                                           format_byte_size(static_cast<uint64_t>(1)<<20));
	Assert("format_byte_size gigabyte",  std::string("1GB"),                                           format_byte_size(static_cast<uint64_t>(1)<<30));
	Assert("format_byte_size terabyte",  std::string("1TB"),                                           format_byte_size(static_cast<uint64_t>(1)<<40));
	Assert("format_byte_size petabyte",  std::string("1PB"),                                           format_byte_size(static_cast<uint64_t>(1)<<50));
	Assert("format_byte_size exabyte",   std::string("1EB"),                                           format_byte_size(static_cast<uint64_t>(1)<<60));
	// Unsupported due to number of byte bigger than currently supported by ISO c++
	//Assert("format_byte_size zettabyte", std::string("1ZB"),                                           format_byte_size(static_cast<uint64_t>(1)<<70));
	//Assert("format_byte_size yottabyte", std::string("1YB"),                                           format_byte_size(static_cast<uint64_t>(1)<<80));
	// uint64_t_MAX == 2**64 == 18446744073709551615I64u == -1
	Assert("format_byte_size max",       std::string("15EB 1023PB 1023TB 1023GB 1023MB 1023KB 1023B"), format_byte_size(static_cast<uint64_t>(-1)));
}

/**
 * @brief Test suite for the format_time output
 */
void format_time_test(void) noexcept {
	// https://en.wikipedia.org/wiki/Unit_of_time
	Assert("format_time null",                      std::string("0s"),                               format_time(static_cast<uint64_t>(0)));
	Assert("format_time second",                    std::string("1s"),                               format_time(static_cast<uint64_t>(1)));
	Assert("format_time decasecond",                std::string("10s"),                              format_time(static_cast<uint64_t>(10)));
	Assert("format_time minute",                    std::string("1m"),                               format_time(static_cast<uint64_t>(60)));
	Assert("format_time milliday",                  std::string("1m 26s"),                           format_time(static_cast<uint64_t>(86))); // missing 0.4s due to precision
	Assert("format_time hectosecond",               std::string("1m 40s"),                           format_time(static_cast<uint64_t>(100)));
	Assert("format_time kilosecond",                std::string("16m 40s"),                          format_time(static_cast<uint64_t>(1e3)));
	Assert("format_time hour",                      std::string("1h"),                               format_time(static_cast<uint64_t>(3600)));
	Assert("format_time day",                       std::string("1j"),                               format_time(static_cast<uint64_t>(86400)));
	Assert("format_time week/sennight",             std::string("1w"),                               format_time(static_cast<uint64_t>(604800)));
	Assert("format_time megasecond",                std::string("1w 4j 13h 46m 40s"),                format_time(static_cast<uint64_t>(1e6)));
	Assert("format_time fortnight",                 std::string("2w"),                               format_time(static_cast<uint64_t>(1209600)));
	Assert("format_time lunar month (draconitic)",  std::string("3w 6j 5h 5m 35s"),                  format_time(static_cast<uint64_t>(2351135))); // missing 0.8 due to precision
	Assert("format_time lunar month (tropical)",    std::string("3w 6j 7h 43m 4s"),                  format_time(static_cast<uint64_t>(2360584))); // missing 0.7 due to precision
	Assert("format_time lunar month (sidereal)",    std::string("3w 6j 7h 43m 11s"),                 format_time(static_cast<uint64_t>(2360591))); // missing 0.6 to precision
	Assert("format_time lunar month (anomalistic)", std::string("3w 6j 13h 18m 33s"),                format_time(static_cast<uint64_t>(2380713))); // missing 0.2 due to precision
	Assert("format_time lunar month (synodic)",     std::string("4w 1j 12h 44m 2s"),                 format_time(static_cast<uint64_t>(2551442))); // missing 0.9 due to precision
	Assert("format_time month",                     std::string("1M"),                               format_time(static_cast<uint64_t>(2678400)));
	Assert("format_time quarantine",                std::string("1M 1w 2j"),                         format_time(static_cast<uint64_t>(3456e3)));
	Assert("format_time semester",                  std::string("4M 2j"),                            format_time(static_cast<uint64_t>(10886400)));
	Assert("format_time lunar year",                std::string("11M 1w 6j 8h 52m 48s"),             format_time(static_cast<uint64_t>(30617568)));
	Assert("format_time year",                      std::string("1y"),                               format_time(static_cast<uint64_t>(31536e3)));
	Assert("format_time tropical year",             std::string("1y 5h 48m 45s"),                    format_time(static_cast<uint64_t>(31556925))); // missing 0.216 due to precision
	Assert("format_time gregorian year",            std::string("1y 5h 49m 12s"),                    format_time(static_cast<uint64_t>(31556952)));
	Assert("format_time sidereal year",             std::string("1y 6h 9m 9s"),                      format_time(static_cast<uint64_t>(31558149))); // missing 0.7635456 due to precision
	Assert("format_time leap year",                 std::string("1y 1j"),                            format_time(static_cast<uint64_t>(31622400)));
	Assert("format_time olympiad",                  std::string("4y"),                               format_time(static_cast<uint64_t>(126144e3)));
	Assert("format_time lusturm",                   std::string("5y"),                               format_time(static_cast<uint64_t>(15768e4)));
	Assert("format_time decade",                    std::string("10y"),                              format_time(static_cast<uint64_t>(31536e4)));
	Assert("format_time indiction",                 std::string("15y"),                              format_time(static_cast<uint64_t>(47304e4)));
	Assert("format_time score",                     std::string("20y"),                              format_time(static_cast<uint64_t>(63072e4)));
	Assert("format_time gigasecond",                std::string("31y 8M 1w 4j 1h 46m 40s"),          format_time(static_cast<uint64_t>(1e9)));
	Assert("format_time jubilee",                   std::string("50y"),                              format_time(static_cast<uint64_t>(15768e5)));
	Assert("format_time century",                   std::string("1c"),                               format_time(static_cast<uint64_t>(31536e5)));
	Assert("format_time millennium",                std::string("10c"),                              format_time(static_cast<uint64_t>(31536e6)));
	Assert("format_time age",                       std::string("257c 72y"),                         format_time(static_cast<uint64_t>(812745792e3)));
	Assert("format_time terasecond",                std::string("3170c 97y 10M 3w 4j 17h 46m 40s"),  format_time(static_cast<uint64_t>(1e13)));
	Assert("format_time megaannum",                 std::string("10000c"),                           format_time(static_cast<uint64_t>(31536e9)));
	Assert("format_time petasecond",                std::string("317097c 91y 11M 2w 4j 1h 46m 40s"), format_time(static_cast<uint64_t>(1e15)));
	Assert("format_time galactic year",             std::string("2300000c"),                         format_time(static_cast<uint64_t>(725328e10)));
	Assert("format_time eon",                       std::string("10000000c"),                        format_time(static_cast<uint64_t>(31536e12)));
	Assert("format_time kalpa",                     std::string("43200000c"),                        format_time(static_cast<uint64_t>(13623552e10)));
	Assert("format_time exasecond",                 std::string("317097919c 83y 9M 1h 46m 40s"),     format_time(static_cast<uint64_t>(1e18)));
	// Cannot use number bigger than currently supported ISO C++
	//Assert("format_time zettasecond",               std::string(""),                                 format_time(static_cast<uint64_t>(1e21)));
	//Assert("format_time yottasecond",               std::string(""),                                 format_time(static_cast<uint64_t>(1e24)));
	//Assert("format_time ronnasecond",               std::string(""),                                 format_time(static_cast<uint64_t>(1e27)));
	//Assert("format_time quettasecond",              std::string(""),                                 format_time(static_cast<uint64_t>(1e30)));
	// uint64_t_MAX == 2**64 == 18446744073709551615I64u == -1
	Assert("format_time max",                       std::string("5849424173c 55y 3w 5j 7h 15s"),     format_time(static_cast<uint64_t>(-1)));
}

/**
 * @brief Test suite for the format_time_ns output
 */
void format_time_ns_test(void) noexcept {
	// https://en.wikipedia.org/wiki/Unit_of_time
	Assert("format_time_ns null",                      std::string("0ns"),                                         format_time_ns(static_cast<uint64_t>(0)));
	Assert("format_time_ns nanosecond",                std::string("1ns"),                                         format_time_ns(static_cast<uint64_t>(1)));
	Assert("format_time_ns shake",                     std::string("10ns"),                                        format_time_ns(static_cast<uint64_t>(10)));
	Assert("format_time_ns microsecond",               std::string("1us"),                                         format_time_ns(static_cast<uint64_t>(1e3)));
	Assert("format_time_ns millisecond",               std::string("1ms"),                                         format_time_ns(static_cast<uint64_t>(1e6)));
	Assert("format_time_ns centisecond",               std::string("10ms"),                                        format_time_ns(static_cast<uint64_t>(1e7)));
	Assert("format_time_ns decisecond",                std::string("100ms"),                                       format_time_ns(static_cast<uint64_t>(1e8)));
	Assert("format_time_ns second",                    std::string("1s"),                                          format_time_ns(static_cast<uint64_t>(1e9)));
	Assert("format_time_ns decasecond",                std::string("10s"),                                         format_time_ns(static_cast<uint64_t>(1e10)));
	Assert("format_time_ns minute",                    std::string("1m"),                                          format_time_ns(static_cast<uint64_t>(6e10)));
	Assert("format_time_ns milliday",                  std::string("1m 26s 400ms"),                                format_time_ns(static_cast<uint64_t>(864e8)));
	Assert("format_time_ns hectosecond",               std::string("1m 40s"),                                      format_time_ns(static_cast<uint64_t>(1e11)));
	Assert("format_time_ns kilosecond",                std::string("16m 40s"),                                     format_time_ns(static_cast<uint64_t>(1e12)));
	Assert("format_time_ns hour",                      std::string("1h"),                                          format_time_ns(static_cast<uint64_t>(36e11)));
	Assert("format_time_ns day",                       std::string("1j"),                                          format_time_ns(static_cast<uint64_t>(864e11)));
	Assert("format_time_ns week/sennight",             std::string("1w"),                                          format_time_ns(static_cast<uint64_t>(6048e11)));
	Assert("format_time_ns megasecond",                std::string("1w 4j 13h 46m 40s"),                           format_time_ns(static_cast<uint64_t>(1e15)));
	Assert("format_time_ns fortnight",                 std::string("2w"),                                          format_time_ns(static_cast<uint64_t>(12096e11)));
	Assert("format_time_ns lunar month (draconitic)",  std::string("3w 6j 5h 5m 35s 800ms"),                       format_time_ns(static_cast<uint64_t>(23511358e8)));
	Assert("format_time_ns lunar month (tropical)",    std::string("3w 6j 7h 43m 4s 700ms"),                       format_time_ns(static_cast<uint64_t>(23605847e8)));
	Assert("format_time_ns lunar month (sidereal)",    std::string("3w 6j 7h 43m 11s 600ms"),                      format_time_ns(static_cast<uint64_t>(23605916e8)));
	Assert("format_time_ns lunar month (anomalistic)", std::string("3w 6j 13h 18m 33s 200ms"),                     format_time_ns(static_cast<uint64_t>(23807132e8)));
	Assert("format_time_ns lunar month (synodic)",     std::string("4w 1j 12h 44m 2s 900ms"),                      format_time_ns(static_cast<uint64_t>(25514429e8)));
	Assert("format_time_ns month",                     std::string("1M"),                                          format_time_ns(static_cast<uint64_t>(26784e11)));
	Assert("format_time_ns quarantine",                std::string("1M 1w 2j"),                                    format_time_ns(static_cast<uint64_t>(3456e12)));
	Assert("format_time_ns semester",                  std::string("4M 2j"),                                       format_time_ns(static_cast<uint64_t>(108864e11)));
	Assert("format_time_ns lunar year",                std::string("11M 1w 6j 8h 52m 48s"),                        format_time_ns(static_cast<uint64_t>(30617568e9)));
	Assert("format_time_ns year",                      std::string("1y"),                                          format_time_ns(static_cast<uint64_t>(31536e12)));
	Assert("format_time_ns tropical year",             std::string("1y 5h 48m 45s 216ms"),                         format_time_ns(static_cast<uint64_t>(31556925216e6)));
	Assert("format_time_ns gregorian year",            std::string("1y 5h 49m 12s"),                               format_time_ns(static_cast<uint64_t>(31556952e9)));
	Assert("format_time_ns sidereal year",             std::string("1y 6h 9m 9s 763ms 545us 600ns"),               format_time_ns(static_cast<uint64_t>(315581497635456e2)));
	Assert("format_time_ns leap year",                 std::string("1y 1j"),                                       format_time_ns(static_cast<uint64_t>(316224e11)));
	Assert("format_time_ns olympiad",                  std::string("4y"),                                          format_time_ns(static_cast<uint64_t>(126144e12)));
	Assert("format_time_ns lusturm",                   std::string("5y"),                                          format_time_ns(static_cast<uint64_t>(15768e13)));
	Assert("format_time_ns decade",                    std::string("10y"),                                         format_time_ns(static_cast<uint64_t>(31536e13)));
	Assert("format_time_ns indiction",                 std::string("15y"),                                         format_time_ns(static_cast<uint64_t>(47304e13)));
	Assert("format_time_ns score",                     std::string("20y"),                                         format_time_ns(static_cast<uint64_t>(63072e13)));
	Assert("format_time_ns gigasecond",                std::string("31y 8M 1w 4j 1h 46m 40s"),                     format_time_ns(static_cast<uint64_t>(1e18)));
	Assert("format_time_ns jubilee",                   std::string("50y"),                                         format_time_ns(static_cast<uint64_t>(15768e14)));
	Assert("format_time_ns century",                   std::string("1c"),                                          format_time_ns(static_cast<uint64_t>(31536e14)));
	// Cannot use number bigger than currently supported ISO C++
	//Assert("format_time_ns millennium",                std::string("10c"),                                         format_time_ns(static_cast<uint64_t>(31536e15)));
	//Assert("format_time_ns age",                       std::string("257c 72y"),                                    format_time_ns(static_cast<uint64_t>(812745792e12)));
	//Assert("format_time_ns terasecond",                std::string("3170c 97y 10M 3w 4j 17h 46m 40s"),             format_time_ns(static_cast<uint64_t>(1e22)));
	//Assert("format_time_ns megaannum",                 std::string("10000c"),                                      format_time_ns(static_cast<uint64_t>(31536e18)));
	//Assert("format_time_ns petasecond",                std::string("317097c 91y 11M 2w 4j 1h 46m 40s"),            format_time_ns(static_cast<uint64_t>(1e24)));
	//Assert("format_time_ns galactic year",             std::string("2300000c"),                                    format_time_ns(static_cast<uint64_t>(725328e19)));
	//Assert("format_time_ns eon",                       std::string("10000000c"),                                   format_time_ns(static_cast<uint64_t>(31536e21)));
	//Assert("format_time_ns kalpa",                     std::string("43200000c"),                                   format_time_ns(static_cast<uint64_t>(13623552e19)));
	//Assert("format_time_ns exasecond",                 std::string("317097919c 83y 9M 1h 46m 40s"),                format_time_ns(static_cast<uint64_t>(1e27)));
	//Assert("format_time_ns zettasecond",               std::string(""),                                            format_time_ns(static_cast<uint64_t>(1e30)));
	//Assert("format_time_ns yottasecond",               std::string(""),                                            format_time_ns(static_cast<uint64_t>(1e33)));
	//Assert("format_time_ns ronnasecond",               std::string(""),                                            format_time_ns(static_cast<uint64_t>(1e36)));
	//Assert("format_time_ns quettasecond",              std::string(""),                                            format_time_ns(static_cast<uint64_t>(1e39)));
	// uint64_t_MAX == 2**64 == 18446744073709551615I64u == -1
	Assert("format_time_ns max",                       std::string("5c 84y 11M 2j 23h 34m 33s 709ms 551us 615ns"), format_time_ns(static_cast<uint64_t>(-1)));
}

/**
 * @brief Test suite for the thousand_sep output
 */
void thousand_sep_test(void) noexcept {
	// https://en.wikipedia.org/wiki/Names_of_large_numbers
	Assert("thousand_sep null",                std::string("0"),                                           thousand_sep(static_cast<uint64_t>(0)));
	Assert("thousand_sep unit",                std::string("1"),                                           thousand_sep(static_cast<uint64_t>(1)));
	Assert("thousand_sep ten",                 std::string("10"),                                          thousand_sep(static_cast<uint64_t>(10)));
	Assert("thousand_sep hundred",             std::string("100"),                                         thousand_sep(static_cast<uint64_t>(100)));
	Assert("thousand_sep thousand",            std::string("1,000"),                                       thousand_sep(static_cast<uint64_t>(1e3)));
	Assert("thousand_sep ten thousand",        std::string("10,000"),                                      thousand_sep(static_cast<uint64_t>(1e4)));
	Assert("thousand_sep hundred thousand",    std::string("100,000"),                                     thousand_sep(static_cast<uint64_t>(1e5)));
	Assert("thousand_sep million",             std::string("1,000,000"),                                   thousand_sep(static_cast<uint64_t>(1e6)));
	Assert("thousand_sep ten million",         std::string("10,000,000"),                                  thousand_sep(static_cast<uint64_t>(1e7)));
	Assert("thousand_sep hundred million",     std::string("100,000,000"),                                 thousand_sep(static_cast<uint64_t>(1e8)));
	Assert("thousand_sep billion",             std::string("1,000,000,000"),                               thousand_sep(static_cast<uint64_t>(1e9)));
	Assert("thousand_sep ten billion",         std::string("10,000,000,000"),                              thousand_sep(static_cast<uint64_t>(1e10)));
	Assert("thousand_sep hundred billion",     std::string("100,000,000,000"),                             thousand_sep(static_cast<uint64_t>(1e11)));
	Assert("thousand_sep trillion",            std::string("1,000,000,000,000"),                           thousand_sep(static_cast<uint64_t>(1e12)));
	Assert("thousand_sep ten trillion",        std::string("10,000,000,000,000"),                          thousand_sep(static_cast<uint64_t>(1e13)));
	Assert("thousand_sep hundred trillion",    std::string("100,000,000,000,000"),                         thousand_sep(static_cast<uint64_t>(1e14)));
	Assert("thousand_sep quadrillion",         std::string("1,000,000,000,000,000"),                       thousand_sep(static_cast<uint64_t>(1e15)));
	Assert("thousand_sep ten quadrillion",     std::string("10,000,000,000,000,000"),                      thousand_sep(static_cast<uint64_t>(1e16)));
	Assert("thousand_sep hundred quadrillion", std::string("100,000,000,000,000,000"),                     thousand_sep(static_cast<uint64_t>(1e17)));
	Assert("thousand_sep quintillion",         std::string("1,000,000,000,000,000,000"),                   thousand_sep(static_cast<uint64_t>(1e18)));
	Assert("thousand_sep ten quintillion",     std::string("10,000,000,000,000,000,000"),                  thousand_sep(static_cast<uint64_t>(1e19)));
	// Cannot use number bigger than currently supported ISO C++
	//Assert("thousand_sep hundred quintillion", std::string("100,000,000,000,000,000,000"),                 thousand_sep(static_cast<uint64_t>(1e20)));
	//Assert("thousand_sep sextillion",          std::string("1,000,000,000,000,000,000,000"),               thousand_sep(static_cast<uint64_t>(1e21)));
	//Assert("thousand_sep ten sextillion",      std::string("10,000,000,000,000,000,000,000"),              thousand_sep(static_cast<uint64_t>(1e22)));
	//Assert("thousand_sep hundred sextillion",  std::string("100,000,000,000,000,000,000,000"),             thousand_sep(static_cast<uint64_t>(1e23)));
	//Assert("thousand_sep septillion",          std::string("1,000,000,000,000,000,000,000,000"),           thousand_sep(static_cast<uint64_t>(1e24)));
	//Assert("thousand_sep ten septillion",      std::string("10,000,000,000,000,000,000,000,000"),          thousand_sep(static_cast<uint64_t>(1e25)));
	//Assert("thousand_sep hundred septillion",  std::string("100,000,000,000,000,000,000,000,000"),         thousand_sep(static_cast<uint64_t>(1e26)));
	//Assert("thousand_sep octillion",           std::string("1,000,000,000,000,000,000,000,000,000"),       thousand_sep(static_cast<uint64_t>(1e27)));
	//Assert("thousand_sep ten octillion",       std::string("10,000,000,000,000,000,000,000,000,000"),      thousand_sep(static_cast<uint64_t>(1e28)));
	//Assert("thousand_sep hundred octillion",   std::string("100,000,000,000,000,000,000,000,000,000"),     thousand_sep(static_cast<uint64_t>(1e29)));
	//Assert("thousand_sep nonillion",           std::string("1,000,000,000,000,000,000,000,000,000,000"),   thousand_sep(static_cast<uint64_t>(1e30)));
	//Assert("thousand_sep ten nonillion",       std::string("10,000,000,000,000,000,000,000,000,000,000"),  thousand_sep(static_cast<uint64_t>(1e31)));
	//Assert("thousand_sep hundred nonillion",   std::string("100,000,000,000,000,000,000,000,000,000,000"), thousand_sep(static_cast<uint64_t>(1e32)));
	Assert("thousand_sep order",               std::string("1,234,567,890"),                               thousand_sep(static_cast<uint64_t>(1234567890)));
	Assert("thousand_sep reverse order",       std::string("9,876,543,210"),                               thousand_sep(static_cast<uint64_t>(9876543210)));
	// uint64_t_MAX == 2**64 == 18446744073709551615I64u == -1
	Assert("thousand_sep max",                 std::string("18,446,744,073,709,551,615"),                  thousand_sep(static_cast<uint64_t>(-1)));
}