#include <base/mem.h>
#include <base/str.h>
#include <base/windows.h>

#include <engine/server/server.h>

#include <game/gamecore.h>

#include <gtest/gtest.h>

#include <limits>

typedef void (*TStringArgumentFunction)(char *pStr);
template<TStringArgumentFunction Func>
static void TestInplace(const char *pInput, const char *pOutput)
{
	char aBuf[512];
	str_copy(aBuf, pInput);
	Func(aBuf);
	EXPECT_STREQ(aBuf, pOutput);
}

TEST(Str, StrDelim)
{
	int Start, End;
	//                                      0123456
	//                            01234567891111111
	EXPECT_EQ(str_delimiters_around_offset("123;123456789;aaa", ";", 5, &Start, &End), true);
	EXPECT_EQ(Start, 4);
	EXPECT_EQ(End, 13);

	EXPECT_EQ(str_delimiters_around_offset("123;123", ";", 1, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 3);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 1, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 0);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 2, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 0);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 3, &Start, &End), true);
	EXPECT_EQ(Start, 3);
	EXPECT_EQ(End, 6);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 4, &Start, &End), true);
	EXPECT_EQ(Start, 3);
	EXPECT_EQ(End, 6);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 9, &Start, &End), true);
	EXPECT_EQ(Start, 9);
	EXPECT_EQ(End, 12);

	EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 22, &Start, &End), false);
	EXPECT_EQ(Start, 21);
	EXPECT_EQ(End, 26);

	EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 2, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 3);

	EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 3, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 3);

	EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 4, &Start, &End), true);
	EXPECT_EQ(Start, 4);
	EXPECT_EQ(End, 4);

	EXPECT_EQ(str_delimiters_around_offset("", ";", 4, &Start, &End), false);
	EXPECT_EQ(Start, 0);
	EXPECT_EQ(End, 0);
}

TEST(Str, StrIsNum)
{
	EXPECT_EQ(str_isnum('/'), false);
	EXPECT_EQ(str_isnum('0'), true);
	EXPECT_EQ(str_isnum('1'), true);
	EXPECT_EQ(str_isnum('2'), true);
	EXPECT_EQ(str_isnum('8'), true);
	EXPECT_EQ(str_isnum('9'), true);
	EXPECT_EQ(str_isnum(':'), false);
	EXPECT_EQ(str_isnum(' '), false);
}

TEST(Str, StrIsAllNum)
{
	EXPECT_EQ(str_isallnum("/"), 0);
	EXPECT_EQ(str_isallnum("0"), 1);
	EXPECT_EQ(str_isallnum("1"), 1);
	EXPECT_EQ(str_isallnum("2"), 1);
	EXPECT_EQ(str_isallnum("8"), 1);
	EXPECT_EQ(str_isallnum("9"), 1);
	EXPECT_EQ(str_isallnum(":"), 0);
	EXPECT_EQ(str_isallnum(" "), 0);

	EXPECT_EQ(str_isallnum("123"), 1);
	EXPECT_EQ(str_isallnum("123/"), 0);
	EXPECT_EQ(str_isallnum("123:"), 0);
}

TEST(Str, Dist)
{
	EXPECT_EQ(str_utf8_dist("aaa", "aaa"), 0);
	EXPECT_EQ(str_utf8_dist("123", "123"), 0);
	EXPECT_EQ(str_utf8_dist("", ""), 0);
	EXPECT_EQ(str_utf8_dist("a", "b"), 1);
	EXPECT_EQ(str_utf8_dist("", "aaa"), 3);
	EXPECT_EQ(str_utf8_dist("123", ""), 3);
	EXPECT_EQ(str_utf8_dist("ä", ""), 1);
	EXPECT_EQ(str_utf8_dist("Hëllö", "Hello"), 2);
	// https://en.wikipedia.org/w/index.php?title=Levenshtein_distance&oldid=828480025#Example
	EXPECT_EQ(str_utf8_dist("kitten", "sitting"), 3);
	EXPECT_EQ(str_utf8_dist("flaw", "lawn"), 2);
	EXPECT_EQ(str_utf8_dist("saturday", "sunday"), 3);
}

TEST(Str, Utf8Isspace)
{
	EXPECT_TRUE(str_utf8_isspace(0x200b)); // Zero-width space
	EXPECT_TRUE(str_utf8_isspace(' '));
	EXPECT_FALSE(str_utf8_isspace('a'));
	// Control characters.
	for(char c = 0; c < 0x20; c++)
	{
		EXPECT_TRUE(str_utf8_isspace(c));
	}
}

TEST(Str, Utf8SkipWhitespaces)
{
	EXPECT_STREQ(str_utf8_skip_whitespaces("abc"), "abc");
	EXPECT_STREQ(str_utf8_skip_whitespaces("abc   "), "abc   ");
	EXPECT_STREQ(str_utf8_skip_whitespaces("    abc"), "abc");
	EXPECT_STREQ(str_utf8_skip_whitespaces("\xe2\x80\x8b abc"), "abc");
}

TEST(Str, Utf8TrimRight)
{
	char A1[] = "abc";
	str_utf8_trim_right(A1);
	EXPECT_STREQ(A1, "abc");
	char A2[] = "   abc";
	str_utf8_trim_right(A2);
	EXPECT_STREQ(A2, "   abc");
	char A3[] = "abc   ";
	str_utf8_trim_right(A3);
	EXPECT_STREQ(A3, "abc");
	char A4[] = "abc \xe2\x80\x8b";
	str_utf8_trim_right(A4);
	EXPECT_STREQ(A4, "abc");
}

TEST(Str, Utf8CompConfusables)
{
	EXPECT_TRUE(str_utf8_comp_confusable("abc", "abc") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("rn", "m") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("m", "rn") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("rna", "ma") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("ma", "rna") == 0);
	EXPECT_FALSE(str_utf8_comp_confusable("mA", "rna") == 0);
	EXPECT_FALSE(str_utf8_comp_confusable("ma", "rnA") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("arn", "am") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("am", "arn") == 0);
	EXPECT_FALSE(str_utf8_comp_confusable("Am", "arn") == 0);
	EXPECT_FALSE(str_utf8_comp_confusable("am", "Arn") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("l", "ӏ") == 0); // CYRILLIC SMALL LETTER PALOCHKA
	EXPECT_TRUE(str_utf8_comp_confusable("i", "¡") == 0); // INVERTED EXCLAMATION MARK
	EXPECT_FALSE(str_utf8_comp_confusable("o", "x") == 0);
	EXPECT_TRUE(str_utf8_comp_confusable("aceiou", "ąçęįǫų") == 0);
}

TEST(Str, Utf8ToSkeleton)
{
	int aBuf[32];
	EXPECT_EQ(str_utf8_to_skeleton("abc", aBuf, 0), 0);
	EXPECT_EQ(str_utf8_to_skeleton("", aBuf, std::size(aBuf)), 0);
	EXPECT_EQ(str_utf8_to_skeleton("abc", aBuf, std::size(aBuf)), 3);
	EXPECT_EQ(aBuf[0], 'a');
	EXPECT_EQ(aBuf[1], 'b');
	EXPECT_EQ(aBuf[2], 'c');
	EXPECT_EQ(str_utf8_to_skeleton("m", aBuf, std::size(aBuf)), 2);
	EXPECT_EQ(aBuf[0], 'r');
	EXPECT_EQ(aBuf[1], 'n');
	EXPECT_EQ(str_utf8_to_skeleton("rn", aBuf, std::size(aBuf)), 2);
	EXPECT_EQ(aBuf[0], 'r');
	EXPECT_EQ(aBuf[1], 'n');
	EXPECT_EQ(str_utf8_to_skeleton("ӏ", aBuf, std::size(aBuf)), 1); // CYRILLIC SMALL LETTER PALOCHKA
	EXPECT_EQ(aBuf[0], 'i');
	EXPECT_EQ(str_utf8_to_skeleton("¡", aBuf, std::size(aBuf)), 1); // INVERTED EXCLAMATION MARK
	EXPECT_EQ(aBuf[0], 'i');
	EXPECT_EQ(str_utf8_to_skeleton("ąçęįǫų", aBuf, std::size(aBuf)), 6);
	EXPECT_EQ(aBuf[0], 'a');
	EXPECT_EQ(aBuf[1], 'c');
	EXPECT_EQ(aBuf[2], 'e');
	EXPECT_EQ(aBuf[3], 'i');
	EXPECT_EQ(aBuf[4], 'o');
	EXPECT_EQ(aBuf[5], 'u');
}

TEST(Str, Utf8ToLowerCodepoint)
{
	EXPECT_TRUE(str_utf8_tolower_codepoint('A') == 'a');
	EXPECT_TRUE(str_utf8_tolower_codepoint('z') == 'z');
	EXPECT_TRUE(str_utf8_tolower_codepoint(192) == 224); // À -> à
	EXPECT_TRUE(str_utf8_tolower_codepoint(7882) == 7883); // Ị -> ị
}

template<size_t BufferSize = 128>
static void TestStrUtf8ToLower(const char *pInput, const char *pOutput)
{
	char aBuf[BufferSize];
	str_utf8_tolower(pInput, aBuf, sizeof(aBuf));
	EXPECT_STREQ(aBuf, pOutput);
}

TEST(Str, Utf8ToLower)
{
	// See https://stackoverflow.com/a/18689585
	TestStrUtf8ToLower<>("", "");
	TestStrUtf8ToLower<>("a", "a");
	TestStrUtf8ToLower<>("A", "a");
	TestStrUtf8ToLower<>("z", "z");
	TestStrUtf8ToLower<>("Z", "z");
	TestStrUtf8ToLower<>("ABC", "abc");
	TestStrUtf8ToLower<>("ÖÜÄẞ", "öüäß");
	TestStrUtf8ToLower<>("Iİ", "ii");
	TestStrUtf8ToLower<>("Ϊ", "ϊ");
	TestStrUtf8ToLower<>("Į", "į");
	TestStrUtf8ToLower<>("Җ", "җ");
	TestStrUtf8ToLower<>("Ѹ", "ѹ");
	TestStrUtf8ToLower<>("Ǆ", "ǆ");
	TestStrUtf8ToLower<>("ⒹⒹＮＥＴ", "ⓓⓓｎｅｔ");
	TestStrUtf8ToLower<>("Ⱥ", "ⱥ"); // lower case uses more bytes than upper case

	TestStrUtf8ToLower<1>("ABC", "");
	TestStrUtf8ToLower<2>("ABC", "a");
	TestStrUtf8ToLower<3>("ABC", "ab");
	TestStrUtf8ToLower<4>("ABC", "abc");

	TestStrUtf8ToLower<1>("ȺȺȺ", "");
	TestStrUtf8ToLower<2>("ȺȺȺ", "");
	TestStrUtf8ToLower<3>("ȺȺȺ", "");
	TestStrUtf8ToLower<4>("ȺȺȺ", "ⱥ");
	TestStrUtf8ToLower<5>("ȺȺȺ", "ⱥ");
	TestStrUtf8ToLower<6>("ȺȺȺ", "ⱥ");
	TestStrUtf8ToLower<7>("ȺȺȺ", "ⱥⱥ");
	TestStrUtf8ToLower<8>("ȺȺȺ", "ⱥⱥ");
	TestStrUtf8ToLower<9>("ȺȺȺ", "ⱥⱥ");
	TestStrUtf8ToLower<10>("ȺȺȺ", "ⱥⱥⱥ");
	TestStrUtf8ToLower<11>("ȺȺȺ", "ⱥⱥⱥ");
}

TEST(Str, Utf8CompNocase)
{
	EXPECT_TRUE(str_utf8_comp_nocase("ÖlÜ", "ölü") == 0);
	EXPECT_TRUE(str_utf8_comp_nocase("ÜlÖ", "ölü") > 0); // ü > ö
	EXPECT_TRUE(str_utf8_comp_nocase("ÖlÜ", "ölüa") < 0); // \0 < a
	EXPECT_TRUE(str_utf8_comp_nocase("ölüa", "ÖlÜ") > 0); // a < \0

#if (CHAR_MIN < 0)
	const char a[2] = {CHAR_MIN, 0};
	const char b[2] = {0, 0};
	EXPECT_TRUE(str_utf8_comp_nocase(a, b) > 0);
	EXPECT_TRUE(str_utf8_comp_nocase(b, a) < 0);
#endif

	EXPECT_TRUE(str_utf8_comp_nocase_num("ÖlÜ", "ölüa", 5) == 0);
	EXPECT_TRUE(str_utf8_comp_nocase_num("ÖlÜ", "ölüa", 6) != 0);
	EXPECT_TRUE(str_utf8_comp_nocase_num("a", "z", 0) == 0);
	EXPECT_TRUE(str_utf8_comp_nocase_num("a", "z", 1) != 0);
}

TEST(Str, Utf8FindNocase)
{
	const char *pStr = "abc";
	const char *pEnd;
	EXPECT_EQ(str_utf8_find_nocase(pStr, "a", &pEnd), pStr);
	EXPECT_EQ(pEnd, pStr + str_length("a"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "b", &pEnd), pStr + str_length("a"));
	EXPECT_EQ(pEnd, pStr + str_length("ab"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "c", &pEnd), pStr + str_length("ab"));
	EXPECT_EQ(pEnd, pStr + str_length("abc"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "d", &pEnd), nullptr);
	EXPECT_EQ(pEnd, nullptr);

	EXPECT_EQ(str_utf8_find_nocase(pStr, "A", &pEnd), pStr);
	EXPECT_EQ(pEnd, pStr + str_length("a"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "B", &pEnd), pStr + str_length("a"));
	EXPECT_EQ(pEnd, pStr + str_length("ab"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "C", &pEnd), pStr + str_length("ab"));
	EXPECT_EQ(pEnd, pStr + str_length("abc"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "D", &pEnd), nullptr);
	EXPECT_EQ(pEnd, nullptr);

	pStr = "ÄÖÜ";
	EXPECT_EQ(str_utf8_find_nocase(pStr, "ä", &pEnd), pStr);
	EXPECT_EQ(pEnd, pStr + str_length("Ä"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "ö", &pEnd), pStr + str_length("Ä"));
	EXPECT_EQ(pEnd, pStr + str_length("ÄÖ"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "ü", &pEnd), pStr + str_length("ÄÖ"));
	EXPECT_EQ(pEnd, pStr + str_length("ÄÖÜ"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "z", &pEnd), nullptr);
	EXPECT_EQ(pEnd, nullptr);

	// Both 'I' and 'İ' map to 'i'
	pStr = "antimatter";
	EXPECT_EQ(str_utf8_find_nocase(pStr, "I", &pEnd), pStr + str_length("ant"));
	EXPECT_EQ(pEnd, pStr + str_length("anti"));
	EXPECT_EQ(str_utf8_find_nocase(pStr, "İ", &pEnd), pStr + str_length("ant"));
	EXPECT_EQ(pEnd, pStr + str_length("anti"));
	pStr = "ANTIMATTER";
	EXPECT_EQ(str_utf8_find_nocase(pStr, "i", &pEnd), pStr + str_length("ANT"));
	EXPECT_EQ(pEnd, pStr + str_length("ANTI"));
	pStr = "ANTİMATTER";
	EXPECT_EQ(str_utf8_find_nocase(pStr, "i", &pEnd), pStr + str_length("ANT"));
	EXPECT_EQ(pEnd, pStr + str_length("ANTİ"));
}

TEST(Str, Utf8FixTruncation)
{
	char aaBuf[][32] = {
		"",
		"\xff",
		"abc",
		"abc\xff",
		"blub\xffxyz",
		"привет Наташа\xff",
		"до свидания\xffОлег",
	};
	const char *apExpected[] = {
		"",
		"",
		"abc",
		"abc",
		"blub\xffxyz",
		"привет Наташа",
		"до свидания\xffОлег",
	};
	for(unsigned i = 0; i < std::size(aaBuf); i++)
	{
		EXPECT_EQ(str_utf8_fix_truncation(aaBuf[i]), str_length(apExpected[i]));
		EXPECT_STREQ(aaBuf[i], apExpected[i]);
	}
}

TEST(Str, Startswith)
{
	EXPECT_TRUE(str_startswith("abcdef", "abc"));
	EXPECT_FALSE(str_startswith("abc", "abcdef"));

	EXPECT_TRUE(str_startswith("xyz", ""));
	EXPECT_FALSE(str_startswith("", "xyz"));

	EXPECT_FALSE(str_startswith("house", "home"));
	EXPECT_FALSE(str_startswith("blackboard", "board"));

	EXPECT_TRUE(str_startswith("поплавать", "по"));
	EXPECT_FALSE(str_startswith("плавать", "по"));

	static const char ABCDEFG[] = "abcdefg";
	static const char ABC[] = "abc";
	EXPECT_EQ(str_startswith(ABCDEFG, ABC) - ABCDEFG, str_length(ABC));
}

TEST(Str, StartswithNocase)
{
	EXPECT_TRUE(str_startswith_nocase("Abcdef", "abc"));
	EXPECT_FALSE(str_startswith_nocase("aBc", "abcdef"));

	EXPECT_TRUE(str_startswith_nocase("xYz", ""));
	EXPECT_FALSE(str_startswith_nocase("", "xYz"));

	EXPECT_FALSE(str_startswith_nocase("house", "home"));
	EXPECT_FALSE(str_startswith_nocase("Blackboard", "board"));

	EXPECT_TRUE(str_startswith_nocase("поплавать", "по"));
	EXPECT_FALSE(str_startswith_nocase("плавать", "по"));

	static const char ABCDEFG[] = "aBcdefg";
	static const char ABC[] = "abc";
	EXPECT_EQ(str_startswith_nocase(ABCDEFG, ABC) - ABCDEFG, str_length(ABC));
}

TEST(Str, Endswith)
{
	EXPECT_TRUE(str_endswith("abcdef", "def"));
	EXPECT_FALSE(str_endswith("def", "abcdef"));

	EXPECT_TRUE(str_endswith("xyz", ""));
	EXPECT_FALSE(str_endswith("", "xyz"));

	EXPECT_FALSE(str_endswith("rhyme", "mine"));
	EXPECT_FALSE(str_endswith("blackboard", "black"));

	EXPECT_TRUE(str_endswith("люди", "юди"));
	EXPECT_FALSE(str_endswith("люди", "любовь"));

	static const char ABCDEFG[] = "abcdefg";
	static const char DEFG[] = "defg";
	EXPECT_EQ(str_endswith(ABCDEFG, DEFG) - ABCDEFG,
		str_length(ABCDEFG) - str_length(DEFG));
}

TEST(StrFormat, Positional)
{
	char aBuf[256];

	// normal
	str_format(aBuf, sizeof(aBuf), "%s %s", "first", "second");
	EXPECT_STREQ(aBuf, "first second");

	// normal with positional arguments
	str_format(aBuf, sizeof(aBuf), "%1$s %2$s", "first", "second");
	EXPECT_STREQ(aBuf, "first second");

	// reverse
	str_format(aBuf, sizeof(aBuf), "%2$s %1$s", "first", "second");
	EXPECT_STREQ(aBuf, "second first");

	// duplicate
	str_format(aBuf, sizeof(aBuf), "%1$s %1$s %2$d %1$s %2$d", "str", 1);
	EXPECT_STREQ(aBuf, "str str 1 str 1");
}

TEST(Str, EndswithNocase)
{
	EXPECT_TRUE(str_endswith_nocase("abcdef", "deF"));
	EXPECT_FALSE(str_endswith_nocase("def", "abcdef"));

	EXPECT_TRUE(str_endswith_nocase("xyz", ""));
	EXPECT_FALSE(str_endswith_nocase("", "xyz"));

	EXPECT_FALSE(str_endswith_nocase("rhyme", "minE"));
	EXPECT_FALSE(str_endswith_nocase("blackboard", "black"));

	EXPECT_TRUE(str_endswith_nocase("люди", "юди"));
	EXPECT_FALSE(str_endswith_nocase("люди", "любовь"));

	static const char ABCDEFG[] = "abcdefG";
	static const char DEFG[] = "defg";
	EXPECT_EQ(str_endswith_nocase(ABCDEFG, DEFG) - ABCDEFG,
		str_length(ABCDEFG) - str_length(DEFG));
}

TEST(Str, HexEncode)
{
	char aOut[64];
	const char *pData = "ABCD";
	str_hex(aOut, sizeof(aOut), pData, 0);
	EXPECT_STREQ(aOut, "");
	str_hex(aOut, sizeof(aOut), pData, 1);
	EXPECT_STREQ(aOut, "41 ");
	str_hex(aOut, sizeof(aOut), pData, 2);
	EXPECT_STREQ(aOut, "41 42 ");
	str_hex(aOut, sizeof(aOut), pData, 3);
	EXPECT_STREQ(aOut, "41 42 43 ");
	str_hex(aOut, sizeof(aOut), pData, 4);
	EXPECT_STREQ(aOut, "41 42 43 44 ");

	str_hex(aOut, 1, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex(aOut, 2, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex(aOut, 3, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex(aOut, 4, pData, 4);
	EXPECT_STREQ(aOut, "41 ");
	str_hex(aOut, 5, pData, 4);
	EXPECT_STREQ(aOut, "41 ");
	str_hex(aOut, 6, pData, 4);
	EXPECT_STREQ(aOut, "41 ");
	str_hex(aOut, 7, pData, 4);
	EXPECT_STREQ(aOut, "41 42 ");
	str_hex(aOut, 8, pData, 4);
	EXPECT_STREQ(aOut, "41 42 ");
}

TEST(Str, HexEncodeCstyle)
{
	char aOut[128];
	const char *pData = "ABCD";
	str_hex_cstyle(aOut, sizeof(aOut), pData, 0);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 1);
	EXPECT_STREQ(aOut, "0x41");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 2);
	EXPECT_STREQ(aOut, "0x41, 0x42");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 3);
	EXPECT_STREQ(aOut, "0x41, 0x42, 0x43");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 4);
	EXPECT_STREQ(aOut, "0x41, 0x42, 0x43, 0x44");

	str_hex_cstyle(aOut, 1, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 2, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 3, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 4, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 5, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 6, pData, 4);
	EXPECT_STREQ(aOut, "");
	str_hex_cstyle(aOut, 7, pData, 4);
	EXPECT_STREQ(aOut, "0x41");
	str_hex_cstyle(aOut, 12, pData, 4);
	EXPECT_STREQ(aOut, "0x41");
	str_hex_cstyle(aOut, 13, pData, 4);
	EXPECT_STREQ(aOut, "0x41, 0x42");
	str_hex_cstyle(aOut, 14, pData, 4);
	EXPECT_STREQ(aOut, "0x41, 0x42");

	str_hex_cstyle(aOut, sizeof(aOut), pData, 4, 1);
	EXPECT_STREQ(aOut, "0x41,\n0x42,\n0x43,\n0x44");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 4, 2);
	EXPECT_STREQ(aOut, "0x41, 0x42,\n0x43, 0x44");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 4, 3);
	EXPECT_STREQ(aOut, "0x41, 0x42, 0x43,\n0x44");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 4, 4);
	EXPECT_STREQ(aOut, "0x41, 0x42, 0x43, 0x44");
	str_hex_cstyle(aOut, sizeof(aOut), pData, 4, 500);
	EXPECT_STREQ(aOut, "0x41, 0x42, 0x43, 0x44");
}

TEST(Str, HexDecode)
{
	char aOut[5] = {'a', 'b', 'c', 'd', 0};
	EXPECT_EQ(str_hex_decode(aOut, 0, ""), 0);
	EXPECT_STREQ(aOut, "abcd");
	EXPECT_EQ(str_hex_decode(aOut, 0, " "), 2);
	EXPECT_STREQ(aOut, "abcd");
	EXPECT_EQ(str_hex_decode(aOut, 1, "1"), 2);
	EXPECT_STREQ(aOut + 1, "bcd");
	EXPECT_EQ(str_hex_decode(aOut, 1, "41"), 0);
	EXPECT_STREQ(aOut, "Abcd");
	EXPECT_EQ(str_hex_decode(aOut, 1, "4x"), 1);
	EXPECT_STREQ(aOut + 1, "bcd");
	EXPECT_EQ(str_hex_decode(aOut, 1, "x1"), 1);
	EXPECT_STREQ(aOut + 1, "bcd");
	EXPECT_EQ(str_hex_decode(aOut, 1, "411"), 2);
	EXPECT_STREQ(aOut + 1, "bcd");
	EXPECT_EQ(str_hex_decode(aOut, 4, "41424344"), 0);
	EXPECT_STREQ(aOut, "ABCD");
}

static void StrBase64Str(char *pBuffer, int BufferSize, const char *pString)
{
	str_base64(pBuffer, BufferSize, pString, str_length(pString));
}

TEST(Str, Base64)
{
	char aBuf[128];
	str_base64(aBuf, sizeof(aBuf), "\0", 1);
	EXPECT_STREQ(aBuf, "AA==");
	str_base64(aBuf, sizeof(aBuf), "\0\0", 2);
	EXPECT_STREQ(aBuf, "AAA=");
	str_base64(aBuf, sizeof(aBuf), "\0\0\0", 3);
	EXPECT_STREQ(aBuf, "AAAA");

	StrBase64Str(aBuf, sizeof(aBuf), "");
	EXPECT_STREQ(aBuf, "");

	// https://en.wikipedia.org/w/index.php?title=Base64&oldid=1033503483#Output_padding
	StrBase64Str(aBuf, sizeof(aBuf), "pleasure.");
	EXPECT_STREQ(aBuf, "cGxlYXN1cmUu");
	StrBase64Str(aBuf, sizeof(aBuf), "leasure.");
	EXPECT_STREQ(aBuf, "bGVhc3VyZS4=");
	StrBase64Str(aBuf, sizeof(aBuf), "easure.");
	EXPECT_STREQ(aBuf, "ZWFzdXJlLg==");
	StrBase64Str(aBuf, sizeof(aBuf), "asure.");
	EXPECT_STREQ(aBuf, "YXN1cmUu");
	StrBase64Str(aBuf, sizeof(aBuf), "sure.");
	EXPECT_STREQ(aBuf, "c3VyZS4=");

	StrBase64Str(aBuf, 4, "pleasure.");
	EXPECT_STREQ(aBuf, "cGx");
	StrBase64Str(aBuf, 5, "pleasure.");
	EXPECT_STREQ(aBuf, "cGxl");
	StrBase64Str(aBuf, 6, "pleasure.");
	EXPECT_STREQ(aBuf, "cGxlY");
}

TEST(Str, Base64Decode)
{
	char aOut[17];
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), ""), 0);
	EXPECT_STREQ(aOut, "XXXXXXXXXXXXXXXX");

	// https://en.wikipedia.org/w/index.php?title=Base64&oldid=1033503483#Output_padding
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "cGxlYXN1cmUu"), 9);
	EXPECT_STREQ(aOut, "pleasure.XXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "bGVhc3VyZS4="), 8);
	EXPECT_STREQ(aOut, "leasure.XXXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "ZWFzdXJlLg=="), 7);
	EXPECT_STREQ(aOut, "easure.XXXXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "YXN1cmUu"), 6);
	EXPECT_STREQ(aOut, "asure.XXXXXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "c3VyZS4="), 5);
	EXPECT_STREQ(aOut, "sure.XXXXXXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "////"), 3);
	EXPECT_STREQ(aOut, "\xff\xff\xffXXXXXXXXXXXXX");
	str_copy(aOut, "XXXXXXXXXXXXXXXX");
	EXPECT_EQ(str_base64_decode(aOut, sizeof(aOut), "CQk+"), 3);
	EXPECT_STREQ(aOut, "		>XXXXXXXXXXXXX");
}

TEST(Str, Base64DecodeError)
{
	char aBuf[128];
	// Wrong padding.
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "A"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AA"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "A==="), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "=AAA"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "===="), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA=AAAA"), 0);
	// Invalid characters.
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "----"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "A---"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AA--"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA-"), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAAA "), 0);
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "AAA "), 0);
	// Invalid padding values.
	EXPECT_LT(str_base64_decode(aBuf, sizeof(aBuf), "//=="), 0);
	// Wrong output buffer size.
	EXPECT_LT(str_base64_decode(aBuf, 2, "cGxlYXN1cmUu"), 0);
	EXPECT_LT(str_base64_decode(aBuf, 3, "cGxlYXN1cmUu"), 0);
	EXPECT_LT(str_base64_decode(aBuf, 4, "cGxlYXN1cmUu"), 0);
}

TEST(Str, Tokenize)
{
	char aTest[] = "GER,RUS,ZAF,BRA,CAN";
	const char *apOut[] = {"GER", "RUS", "ZAF", "BRA", "CAN"};
	char aBuf[4];

	int n = 0;
	for(const char *pTok = aTest; (pTok = str_next_token(pTok, ",", aBuf, sizeof(aBuf)));)
		EXPECT_STREQ(apOut[n++], aBuf);

	char aTest2[] = "";
	EXPECT_EQ(str_next_token(aTest2, ",", aBuf, sizeof(aBuf)), nullptr);

	char aTest3[] = "+b";
	str_next_token(aTest3, "+", aBuf, sizeof(aBuf));
	EXPECT_STREQ(aBuf, "b");
}

TEST(Str, InList)
{
	char aTest[] = "GER,RUS,ZAF,BRA,CAN";
	EXPECT_TRUE(str_in_list(aTest, ",", "GER"));
	EXPECT_TRUE(str_in_list(aTest, ",", "RUS"));
	EXPECT_TRUE(str_in_list(aTest, ",", "ZAF"));
	EXPECT_TRUE(str_in_list(aTest, ",", "BRA"));
	EXPECT_TRUE(str_in_list(aTest, ",", "CAN"));

	EXPECT_FALSE(str_in_list(aTest, ",", "CHN"));
	EXPECT_FALSE(str_in_list(aTest, ",", "R,R"));

	EXPECT_FALSE(str_in_list("abc,xyz", ",", "abcdef"));
	EXPECT_FALSE(str_in_list("", ",", ""));
	EXPECT_FALSE(str_in_list("", ",", "xyz"));

	EXPECT_TRUE(str_in_list("FOO,,BAR", ",", ""));
	EXPECT_TRUE(str_in_list("abc,,def", ",", "def"));
}

TEST(Str, Format)
{
	char aBuf[4];
	EXPECT_EQ(str_format(aBuf, 4, "%d:", 9), 2);
	EXPECT_STREQ(aBuf, "9:");
	EXPECT_EQ(str_format(aBuf, 4, "%d: ", 9), 3);
	EXPECT_STREQ(aBuf, "9: ");
	EXPECT_EQ(str_format(aBuf, 4, "%d: ", 99), 3);
	EXPECT_STREQ(aBuf, "99:");
}

TEST(Str, FormatNumber)
{
	char aBuf[16];
	EXPECT_EQ(str_format(aBuf, sizeof(aBuf), "%d", 0), 1);
	EXPECT_STREQ(aBuf, "0");
	EXPECT_EQ(str_format(aBuf, sizeof(aBuf), "%d", std::numeric_limits<int>::min()), 11);
	EXPECT_STREQ(aBuf, "-2147483648");
	EXPECT_EQ(str_format(aBuf, sizeof(aBuf), "%d", std::numeric_limits<int>::max()), 10);
	EXPECT_STREQ(aBuf, "2147483647");
}

TEST(Str, FormatTruncate)
{
	const char *pStr = "DDNet最好了";
	char aBuf[64];
	str_format(aBuf, 7, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet");
	str_format(aBuf, 8, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet");
	str_format(aBuf, 9, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_format(aBuf, 10, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_format(aBuf, 11, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_format(aBuf, 12, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_format(aBuf, 13, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_format(aBuf, 14, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_format(aBuf, 15, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最好了");
	str_format(aBuf, 16, "%s", pStr);
	EXPECT_STREQ(aBuf, "DDNet最好了");
}

TEST(Str, TrimWords)
{
	const char *pStr1 = "aa bb ccc   dddd    eeeee";
	EXPECT_STREQ(str_trim_words(pStr1, 0), "aa bb ccc   dddd    eeeee");
	EXPECT_STREQ(str_trim_words(pStr1, 1), "bb ccc   dddd    eeeee");
	EXPECT_STREQ(str_trim_words(pStr1, 2), "ccc   dddd    eeeee");
	EXPECT_STREQ(str_trim_words(pStr1, 3), "dddd    eeeee");
	EXPECT_STREQ(str_trim_words(pStr1, 4), "eeeee");
	EXPECT_STREQ(str_trim_words(pStr1, 5), "");
	EXPECT_STREQ(str_trim_words(pStr1, 100), "");
	const char *pStr2 = "   aaa  bb   ";
	EXPECT_STREQ(str_trim_words(pStr2, 0), "aaa  bb   ");
	EXPECT_STREQ(str_trim_words(pStr2, 1), "bb   ");
	EXPECT_STREQ(str_trim_words(pStr2, 2), "");
	EXPECT_STREQ(str_trim_words(pStr2, 100), "");
	const char *pStr3 = "\n\naa  bb\t\tccc\r\n\r\ndddd";
	EXPECT_STREQ(str_trim_words(pStr3, 0), "aa  bb\t\tccc\r\n\r\ndddd");
	EXPECT_STREQ(str_trim_words(pStr3, 1), "bb\t\tccc\r\n\r\ndddd");
	EXPECT_STREQ(str_trim_words(pStr3, 2), "ccc\r\n\r\ndddd");
	EXPECT_STREQ(str_trim_words(pStr3, 3), "dddd");
	EXPECT_STREQ(str_trim_words(pStr3, 4), "");
	EXPECT_STREQ(str_trim_words(pStr3, 100), "");
	const char *pStr4 = "";
	EXPECT_STREQ(str_trim_words(pStr4, 0), "");
	EXPECT_STREQ(str_trim_words(pStr4, 1), "");
	EXPECT_STREQ(str_trim_words(pStr4, 2), "");
	const char *pStr5 = "     ";
	EXPECT_STREQ(str_trim_words(pStr5, 0), "");
	EXPECT_STREQ(str_trim_words(pStr5, 1), "");
	EXPECT_STREQ(str_trim_words(pStr5, 2), "");
}

TEST(Str, CopyNum)
{
	const char *pFoo = "Foobaré";
	char aBuf[64];
	str_utf8_truncate(aBuf, 3, pFoo, 1);
	EXPECT_STREQ(aBuf, "F");
	str_utf8_truncate(aBuf, 3, pFoo, 2);
	EXPECT_STREQ(aBuf, "Fo");
	str_utf8_truncate(aBuf, 3, pFoo, 3);
	EXPECT_STREQ(aBuf, "Fo");
	str_utf8_truncate(aBuf, sizeof(aBuf), pFoo, 6);
	EXPECT_STREQ(aBuf, "Foobar");
	str_utf8_truncate(aBuf, sizeof(aBuf), pFoo, 7);
	EXPECT_STREQ(aBuf, "Foobaré");
	str_utf8_truncate(aBuf, sizeof(aBuf), pFoo, 0);
	EXPECT_STREQ(aBuf, "");

	char aBuf2[8];
	str_utf8_truncate(aBuf2, sizeof(aBuf2), pFoo, 7);
	EXPECT_STREQ(aBuf2, "Foobar");
	char aBuf3[9];
	str_utf8_truncate(aBuf3, sizeof(aBuf3), pFoo, 7);
	EXPECT_STREQ(aBuf3, "Foobaré");
}

TEST(Str, Copy)
{
	const char *pStr = "DDNet最好了";
	char aBuf[64];
	str_copy(aBuf, pStr, 7);
	EXPECT_STREQ(aBuf, "DDNet");
	str_copy(aBuf, pStr, 8);
	EXPECT_STREQ(aBuf, "DDNet");
	str_copy(aBuf, pStr, 9);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_copy(aBuf, pStr, 10);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_copy(aBuf, pStr, 11);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_copy(aBuf, pStr, 12);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_copy(aBuf, pStr, 13);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_copy(aBuf, pStr, 14);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_copy(aBuf, pStr, 15);
	EXPECT_STREQ(aBuf, "DDNet最好了");
	str_copy(aBuf, pStr, 16);
	EXPECT_STREQ(aBuf, "DDNet最好了");
	str_copy(aBuf, pStr);
	EXPECT_STREQ(aBuf, "DDNet最好了");
}

TEST(Str, CopyArray)
{
	std::array<char, 512> aBuf;
	str_copy(aBuf, "hello");
	EXPECT_STREQ(aBuf.data(), "hello");

	std::array<char, 8> aSmallBuf;
	str_copy(aSmallBuf, "long string");
	EXPECT_STREQ(aSmallBuf.data(), "long st");
}

TEST(Str, Append)
{
	char aBuf[64];
	aBuf[0] = '\0';
	str_append(aBuf, "DDNet最好了", 7);
	EXPECT_STREQ(aBuf, "DDNet");
	str_append(aBuf, "最", 8);
	EXPECT_STREQ(aBuf, "DDNet");
	str_append(aBuf, "最", 9);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_append(aBuf, "好", 10);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_append(aBuf, "好", 11);
	EXPECT_STREQ(aBuf, "DDNet最");
	str_append(aBuf, "好", 12);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_append(aBuf, "了", 13);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_append(aBuf, "了", 14);
	EXPECT_STREQ(aBuf, "DDNet最好");
	str_append(aBuf, "了", 15);
	EXPECT_STREQ(aBuf, "DDNet最好了");
	str_append(aBuf, "了", 16);
	EXPECT_STREQ(aBuf, "DDNet最好了");
	aBuf[0] = '\0';
	str_append(aBuf, "DDNet最好了");
	EXPECT_STREQ(aBuf, "DDNet最好了");
}

TEST(Str, Utf8Stats)
{
	size_t Size, Count;

	str_utf8_stats("abc", 4, 3, &Size, &Count);
	EXPECT_EQ(Size, 3);
	EXPECT_EQ(Count, 3);

	str_utf8_stats("abc", 2, 3, &Size, &Count);
	EXPECT_EQ(Size, 1);
	EXPECT_EQ(Count, 1);

	str_utf8_stats("", 1, 0, &Size, &Count);
	EXPECT_EQ(Size, 0);
	EXPECT_EQ(Count, 0);

	str_utf8_stats("abcde", 6, 5, &Size, &Count);
	EXPECT_EQ(Size, 5);
	EXPECT_EQ(Count, 5);

	str_utf8_stats("любовь", 13, 6, &Size, &Count);
	EXPECT_EQ(Size, 12);
	EXPECT_EQ(Count, 6);

	str_utf8_stats("abc愛", 7, 4, &Size, &Count);
	EXPECT_EQ(Size, 6);
	EXPECT_EQ(Count, 4);

	str_utf8_stats("abc愛", 6, 4, &Size, &Count);
	EXPECT_EQ(Size, 3);
	EXPECT_EQ(Count, 3);

	str_utf8_stats("любовь", 13, 3, &Size, &Count);
	EXPECT_EQ(Size, 6);
	EXPECT_EQ(Count, 3);
}

TEST(Str, Utf8OffsetBytesToChars)
{
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("", 0), 0);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("", 100), 0);

	EXPECT_EQ(str_utf8_offset_bytes_to_chars("abc", 0), 0);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("abc", 1), 1);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("abc", 2), 2);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("abc", 3), 3);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("abc", 100), 3);

	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 0), 0);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 2), 1);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 4), 2);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 6), 3);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 8), 4);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 10), 5);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 12), 6);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("любовь", 100), 6);

	EXPECT_EQ(str_utf8_offset_bytes_to_chars("DDNet最好了", 5), 5);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("DDNet最好了", 8), 6);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("DDNet最好了", 11), 7);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("DDNet最好了", 14), 8);
	EXPECT_EQ(str_utf8_offset_bytes_to_chars("DDNet最好了", 100), 8);
}

TEST(Str, Utf8OffsetCharsToBytes)
{
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("", 0), 0);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("", 100), 0);

	EXPECT_EQ(str_utf8_offset_chars_to_bytes("abc", 0), 0);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("abc", 1), 1);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("abc", 2), 2);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("abc", 3), 3);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("abc", 100), 3);

	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 0), 0);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 1), 2);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 2), 4);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 3), 6);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 4), 8);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 5), 10);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 6), 12);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("любовь", 100), 12);

	EXPECT_EQ(str_utf8_offset_chars_to_bytes("DDNet最好了", 5), 5);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("DDNet最好了", 6), 8);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("DDNet最好了", 7), 11);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("DDNet最好了", 8), 14);
	EXPECT_EQ(str_utf8_offset_chars_to_bytes("DDNet最好了", 100), 14);
}

TEST(Str, HasCc)
{
	EXPECT_FALSE(str_has_cc(""));
	EXPECT_FALSE(str_has_cc("a"));
	EXPECT_FALSE(str_has_cc("Merhaba dünya!"));

	EXPECT_TRUE(str_has_cc("\n"));
	EXPECT_TRUE(str_has_cc("\r"));
	EXPECT_TRUE(str_has_cc("\t"));
	EXPECT_TRUE(str_has_cc("a\n"));
	EXPECT_TRUE(str_has_cc("a\rb"));
	EXPECT_TRUE(str_has_cc("\tb"));
	EXPECT_TRUE(str_has_cc("\n\n"));
	EXPECT_TRUE(str_has_cc("\x1C"));
	EXPECT_TRUE(str_has_cc("\x1D"));
	EXPECT_TRUE(str_has_cc("\x1E"));
	EXPECT_TRUE(str_has_cc("\x1F"));
}

TEST(Str, SanitizeCc)
{
	TestInplace<str_sanitize_cc>("", "");
	TestInplace<str_sanitize_cc>("a", "a");
	TestInplace<str_sanitize_cc>("Merhaba dünya!", "Merhaba dünya!");
	TestInplace<str_sanitize_cc>("\n", " ");
	TestInplace<str_sanitize_cc>("\r", " ");
	TestInplace<str_sanitize_cc>("\t", " ");
	TestInplace<str_sanitize_cc>("a\n", "a ");
	TestInplace<str_sanitize_cc>("a\rb", "a b");
	TestInplace<str_sanitize_cc>("\tb", " b");
	TestInplace<str_sanitize_cc>("\n\n", "  ");
	TestInplace<str_sanitize_cc>("\x1C", " ");
	TestInplace<str_sanitize_cc>("\x1D", " ");
	TestInplace<str_sanitize_cc>("\x1E", " ");
	TestInplace<str_sanitize_cc>("\x1F", " ");
}

TEST(Str, Sanitize)
{
	TestInplace<str_sanitize>("", "");
	TestInplace<str_sanitize>("a", "a");
	TestInplace<str_sanitize>("Merhaba dünya!", "Merhaba dünya!");
	TestInplace<str_sanitize>("\n", "\n");
	TestInplace<str_sanitize>("\r", "\r");
	TestInplace<str_sanitize>("\t", "\t");
	TestInplace<str_sanitize>("a\n", "a\n");
	TestInplace<str_sanitize>("a\rb", "a\rb");
	TestInplace<str_sanitize>("\tb", "\tb");
	TestInplace<str_sanitize>("\n\n", "\n\n");
	TestInplace<str_sanitize>("\x1C", " ");
	TestInplace<str_sanitize>("\x1D", " ");
	TestInplace<str_sanitize>("\x1E", " ");
	TestInplace<str_sanitize>("\x1F", " ");
}

TEST(Str, SanitizeFilename)
{
	TestInplace<str_sanitize_filename>("", "");
	TestInplace<str_sanitize_filename>("a", "a");
	TestInplace<str_sanitize_filename>("Merhaba dünya!", "Merhaba dünya!");
	TestInplace<str_sanitize_filename>("привет Наташа", "привет Наташа");
	TestInplace<str_sanitize_filename>("ąçęįǫų", "ąçęįǫų");
	TestInplace<str_sanitize_filename>("DDNet最好了", "DDNet最好了");
	TestInplace<str_sanitize_filename>("aβい🐘", "aβい🐘");
	TestInplace<str_sanitize_filename>("foo.bar", "foo.bar");
	TestInplace<str_sanitize_filename>("foo.bar.baz", "foo.bar.baz");
	TestInplace<str_sanitize_filename>(".a..b...c....d", ".a..b...c....d");
	TestInplace<str_sanitize_filename>("\n", " ");
	TestInplace<str_sanitize_filename>("\r", " ");
	TestInplace<str_sanitize_filename>("\t", " ");
	TestInplace<str_sanitize_filename>("a\n", "a ");
	TestInplace<str_sanitize_filename>("a\rb", "a b");
	TestInplace<str_sanitize_filename>("\tb", " b");
	TestInplace<str_sanitize_filename>("\n\n", "  ");
	TestInplace<str_sanitize_filename>("\x1C", " ");
	TestInplace<str_sanitize_filename>("\x1D", " ");
	TestInplace<str_sanitize_filename>("\x1E", " ");
	TestInplace<str_sanitize_filename>("\x1F", " ");
	TestInplace<str_sanitize_filename>("\u007F", " ");
	TestInplace<str_sanitize_filename>("\\", " ");
	TestInplace<str_sanitize_filename>("/", " ");
	TestInplace<str_sanitize_filename>("|", " ");
	TestInplace<str_sanitize_filename>(":", " ");
	TestInplace<str_sanitize_filename>("*", " ");
	TestInplace<str_sanitize_filename>("?", " ");
	TestInplace<str_sanitize_filename>("<", " ");
	TestInplace<str_sanitize_filename>(">", " ");
	TestInplace<str_sanitize_filename>("\"", " ");
	TestInplace<str_sanitize_filename>("\\/|:*?<>\"", "         ");
}

TEST(Str, ValidFilename)
{
	EXPECT_TRUE(str_valid_filename("a"));
	EXPECT_TRUE(str_valid_filename("abc"));
	EXPECT_TRUE(str_valid_filename("abc abc"));
	EXPECT_TRUE(str_valid_filename("aa bb ccc dddd eeeee"));
	EXPECT_TRUE(str_valid_filename("öüä"));
	EXPECT_TRUE(str_valid_filename("привет Наташа"));
	EXPECT_TRUE(str_valid_filename("ąçęįǫų"));
	EXPECT_TRUE(str_valid_filename("DDNet最好了"));
	EXPECT_TRUE(str_valid_filename("aβい🐘"));
	EXPECT_TRUE(str_valid_filename("foo.bar"));
	EXPECT_TRUE(str_valid_filename("foo.bar.baz"));
	EXPECT_TRUE(str_valid_filename(".a..b...c....d"));

	EXPECT_FALSE(str_valid_filename(""));
	EXPECT_FALSE(str_valid_filename("aa\nbb"));
	EXPECT_FALSE(str_valid_filename("aa\rbb"));
	EXPECT_FALSE(str_valid_filename("aa\tbb"));
	EXPECT_FALSE(str_valid_filename("aa\u001Cbb"));
	EXPECT_FALSE(str_valid_filename("aa\u001Dbb"));
	EXPECT_FALSE(str_valid_filename("aa\u001Ebb"));
	EXPECT_FALSE(str_valid_filename("aa\u001Fbb"));
	EXPECT_FALSE(str_valid_filename("aa\u007Fbb"));
	EXPECT_FALSE(str_valid_filename("aa\\bb"));
	EXPECT_FALSE(str_valid_filename("aa/bb"));
	EXPECT_FALSE(str_valid_filename("aa|bb"));
	EXPECT_FALSE(str_valid_filename("aa:bb"));
	EXPECT_FALSE(str_valid_filename("aa*bb"));
	EXPECT_FALSE(str_valid_filename("aa?bb"));
	EXPECT_FALSE(str_valid_filename("aa<bb"));
	EXPECT_FALSE(str_valid_filename("aa>bb"));
	EXPECT_FALSE(str_valid_filename("aa\"bb"));
	EXPECT_FALSE(str_valid_filename("\\/|:*?<>\""));
	EXPECT_FALSE(str_valid_filename("aa bb")); // EM QUAD
	EXPECT_FALSE(str_valid_filename("aa​bb")); // ZERO WIDTH SPACE
	EXPECT_FALSE(str_valid_filename(" abc"));
	EXPECT_FALSE(str_valid_filename("   abc"));
	EXPECT_FALSE(str_valid_filename("abc "));
	EXPECT_FALSE(str_valid_filename("abc   "));
	EXPECT_FALSE(str_valid_filename("abc   abc"));
	EXPECT_FALSE(str_valid_filename(" abc abc "));
	EXPECT_FALSE(str_valid_filename("   abc   abc   "));
	EXPECT_FALSE(str_valid_filename("abc."));
	EXPECT_FALSE(str_valid_filename("abc..."));
	EXPECT_FALSE(str_valid_filename("abc... "));
	EXPECT_FALSE(str_valid_filename("abc ..."));

	// reserved names
	EXPECT_FALSE(str_valid_filename("con"));
	EXPECT_FALSE(str_valid_filename("CON"));
	EXPECT_FALSE(str_valid_filename("cOn"));
	EXPECT_FALSE(str_valid_filename("con.txt"));
	EXPECT_FALSE(str_valid_filename("con.tar.gz"));
	EXPECT_FALSE(str_valid_filename("CON.TAR.GZ"));
	EXPECT_FALSE(str_valid_filename("PRN"));
	EXPECT_FALSE(str_valid_filename("AUX"));
	EXPECT_FALSE(str_valid_filename("NUL"));
	EXPECT_FALSE(str_valid_filename("COM4"));
	EXPECT_FALSE(str_valid_filename("lpt²"));
	// reserved names allowed as prefix if not separated by period
	EXPECT_TRUE(str_valid_filename("console"));
	EXPECT_TRUE(str_valid_filename("console.log"));
	EXPECT_TRUE(str_valid_filename("console.tar.gz"));
	EXPECT_TRUE(str_valid_filename("Auxiliary"));
	EXPECT_TRUE(str_valid_filename("Null"));
	EXPECT_TRUE(str_valid_filename("Null.txt"));
}

TEST(Str, CleanWhitespaces)
{
	TestInplace<str_clean_whitespaces>("aa bb ccc dddd eeeee", "aa bb ccc dddd eeeee");
	TestInplace<str_clean_whitespaces>("     ", "");
	TestInplace<str_clean_whitespaces>("     aa", "aa");
	TestInplace<str_clean_whitespaces>("aa     ", "aa");
	TestInplace<str_clean_whitespaces>("  aa   bb    ccc     dddd       eeeee    ", "aa bb ccc dddd eeeee");
}

TEST(Str, SkipToWhitespace)
{
	char aBuf[64];
	str_copy(aBuf, "");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf);
	str_copy(aBuf, "    a");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf);
	str_copy(aBuf, "aaaa  b");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf + 4);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf + 4);
	str_copy(aBuf, "aaaa\n\nb");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf + 4);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf + 4);
	str_copy(aBuf, "aaaa\r\rb");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf + 4);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf + 4);
	str_copy(aBuf, "aaaa\t\tb");
	EXPECT_EQ(str_skip_to_whitespace(aBuf), aBuf + 4);
	EXPECT_EQ(str_skip_to_whitespace_const(aBuf), aBuf + 4);
}

TEST(Str, SkipWhitespaces)
{
	char aBuf[64];
	str_copy(aBuf, "");
	EXPECT_EQ(str_skip_whitespaces(aBuf), aBuf);
	EXPECT_EQ(str_skip_whitespaces_const(aBuf), aBuf);
	str_copy(aBuf, "aaaa");
	EXPECT_EQ(str_skip_whitespaces(aBuf), aBuf);
	EXPECT_EQ(str_skip_whitespaces_const(aBuf), aBuf);
	str_copy(aBuf, " \n\r\taaaa");
	EXPECT_EQ(str_skip_whitespaces(aBuf), aBuf + 4);
	EXPECT_EQ(str_skip_whitespaces_const(aBuf), aBuf + 4);
}

TEST(Str, CompFilename)
{
	EXPECT_EQ(str_comp_filenames("a", "a"), 0);
	EXPECT_LT(str_comp_filenames("a", "b"), 0);
	EXPECT_GT(str_comp_filenames("b", "a"), 0);
	EXPECT_EQ(str_comp_filenames("A", "a"), 0);
	EXPECT_LT(str_comp_filenames("A", "b"), 0);
	EXPECT_GT(str_comp_filenames("b", "A"), 0);
	EXPECT_LT(str_comp_filenames("a", "B"), 0);
	EXPECT_GT(str_comp_filenames("B", "a"), 0);
	EXPECT_EQ(str_comp_filenames("1A", "1a"), 0);
	EXPECT_LT(str_comp_filenames("1a", "1B"), 0);
	EXPECT_GT(str_comp_filenames("1B", "1a"), 0);
	EXPECT_LT(str_comp_filenames("1a", "1b"), 0);
	EXPECT_GT(str_comp_filenames("1b", "1a"), 0);
	EXPECT_GT(str_comp_filenames("12a", "1B"), 0);
	EXPECT_LT(str_comp_filenames("1B", "12a"), 0);
	EXPECT_GT(str_comp_filenames("10a", "1B"), 0);
	EXPECT_LT(str_comp_filenames("1B", "10a"), 0);
	EXPECT_GT(str_comp_filenames("10a", "00B"), 0);
	EXPECT_LT(str_comp_filenames("00B", "10a"), 0);
	EXPECT_GT(str_comp_filenames("10a", "09B"), 0);
	EXPECT_LT(str_comp_filenames("09B", "10a"), 0);
	EXPECT_LT(str_comp_filenames("abc", "abcd"), 0);
	EXPECT_GT(str_comp_filenames("abcd", "abc"), 0);
	EXPECT_LT(str_comp_filenames("abc2", "abcd1"), 0);
	EXPECT_GT(str_comp_filenames("abcd1", "abc2"), 0);
	EXPECT_LT(str_comp_filenames("abc50", "abcd"), 0);
	EXPECT_GT(str_comp_filenames("abcd", "abc50"), 0);
	EXPECT_EQ(str_comp_filenames("file0", "file0"), 0);
	EXPECT_LT(str_comp_filenames("file0", "file1"), 0);
	EXPECT_GT(str_comp_filenames("file1", "file0"), 0);
	EXPECT_LT(str_comp_filenames("file1", "file09"), 0);
	EXPECT_GT(str_comp_filenames("file09", "file1"), 0);
	EXPECT_LT(str_comp_filenames("file1", "file009"), 0);
	EXPECT_GT(str_comp_filenames("file009", "file1"), 0);
	EXPECT_GT(str_comp_filenames("file10", "file00"), 0);
	EXPECT_LT(str_comp_filenames("file00", "file10"), 0);
	EXPECT_GT(str_comp_filenames("file10", "file09"), 0);
	EXPECT_LT(str_comp_filenames("file09", "file10"), 0);
	EXPECT_LT(str_comp_filenames("file13", "file37"), 0);
	EXPECT_GT(str_comp_filenames("file37", "file13"), 0);
	EXPECT_LT(str_comp_filenames("file1.ext", "file09.ext"), 0);
	EXPECT_GT(str_comp_filenames("file09.ext", "file1.ext"), 0);
	EXPECT_LT(str_comp_filenames("file1.ext", "file009.ext"), 0);
	EXPECT_GT(str_comp_filenames("file009.ext", "file1.ext"), 0);
	EXPECT_EQ(str_comp_filenames("file0.ext", "file0.ext"), 0);
	EXPECT_LT(str_comp_filenames("file13.ext", "file37.ext"), 0);
	EXPECT_GT(str_comp_filenames("file37.ext", "file13.ext"), 0);
	EXPECT_LT(str_comp_filenames("FILE13.EXT", "file37.ext"), 0);
	EXPECT_GT(str_comp_filenames("file37.ext", "FILE13.EXT"), 0);
	EXPECT_GT(str_comp_filenames("file10.ext", "file00.ext"), 0);
	EXPECT_LT(str_comp_filenames("file00.ext", "file10.ext"), 0);
	EXPECT_GT(str_comp_filenames("file10.ext", "file09.ext"), 0);
	EXPECT_LT(str_comp_filenames("file09.ext", "file10.ext"), 0);
	EXPECT_LT(str_comp_filenames("file42", "file1337"), 0);
	EXPECT_GT(str_comp_filenames("file1337", "file42"), 0);
	EXPECT_LT(str_comp_filenames("file42.ext", "file1337.ext"), 0);
	EXPECT_GT(str_comp_filenames("file1337.ext", "file42.ext"), 0);
	EXPECT_GT(str_comp_filenames("file4414520", "file2055"), 0);
	EXPECT_LT(str_comp_filenames("file4414520", "file205523151812419"), 0);
	EXPECT_LT(str_comp_filenames("file1", "file1a"), 0);
	EXPECT_GT(str_comp_filenames("file1a", "file1"), 0);
	EXPECT_LT(str_comp_filenames("Kobra 1", "Kobra 1 v2"), 0);
	EXPECT_GT(str_comp_filenames("Kobra 1 v2", "Kobra 1"), 0);
	EXPECT_LT(str_comp_filenames("Kobra 1 v2", "Kobra 1 v3"), 0);
	EXPECT_GT(str_comp_filenames("Kobra 1 v3", "Kobra 1 v2"), 0);
}

TEST(Str, RightChar)
{
	const char *pStr = "a bb ccc dddd       eeeee";
	EXPECT_EQ(str_rchr(pStr, 'a'), pStr);
	EXPECT_EQ(str_rchr(pStr, 'b'), pStr + 3);
	EXPECT_EQ(str_rchr(pStr, 'c'), pStr + 7);
	EXPECT_EQ(str_rchr(pStr, 'd'), pStr + 12);
	EXPECT_EQ(str_rchr(pStr, ' '), pStr + 19);
	EXPECT_EQ(str_rchr(pStr, 'e'), pStr + 24);
	EXPECT_EQ(str_rchr(pStr, '\0'), pStr + str_length(pStr));
	EXPECT_EQ(str_rchr(pStr, 'y'), nullptr);
}

TEST(Str, CountChar)
{
	const char *pStr = "a bb ccc dddd       eeeee";
	EXPECT_EQ(str_countchr(pStr, 'a'), 1);
	EXPECT_EQ(str_countchr(pStr, 'b'), 2);
	EXPECT_EQ(str_countchr(pStr, 'c'), 3);
	EXPECT_EQ(str_countchr(pStr, 'd'), 4);
	EXPECT_EQ(str_countchr(pStr, 'e'), 5);
	EXPECT_EQ(str_countchr(pStr, ' '), 10);
	EXPECT_EQ(str_countchr(pStr, '\0'), 0);
	EXPECT_EQ(str_countchr(pStr, 'y'), 0);
}

TEST(Str, StrToInts)
{
	int aInts[8];

	StrToInts(aInts, 1, "a");
	EXPECT_EQ(aInts[0], 0xE1808000);

	StrToInts(aInts, 1, "ab");
	EXPECT_EQ(aInts[0], 0xE1E28000);

	StrToInts(aInts, 1, "abc");
	EXPECT_EQ(aInts[0], 0xE1E2E300);

	StrToInts(aInts, 2, "abcd");
	EXPECT_EQ(aInts[0], 0xE1E2E3E4);
	EXPECT_EQ(aInts[1], 0x80808000);

	StrToInts(aInts, 2, "abcde");
	EXPECT_EQ(aInts[0], 0xE1E2E3E4);
	EXPECT_EQ(aInts[1], 0xE5808000);

	StrToInts(aInts, 2, "abcdef");
	EXPECT_EQ(aInts[0], 0xE1E2E3E4);
	EXPECT_EQ(aInts[1], 0xE5E68000);

	StrToInts(aInts, 2, "abcdefg");
	EXPECT_EQ(aInts[0], 0xE1E2E3E4);
	EXPECT_EQ(aInts[1], 0xE5E6E700);

	StrToInts(aInts, 2, "öüä");
	EXPECT_EQ(aInts[0], 0x4336433C);
	EXPECT_EQ(aInts[1], 0x43248000);

	StrToInts(aInts, 3, "aβい🐘");
	EXPECT_EQ(aInts[0], 0xE14E3263);
	EXPECT_EQ(aInts[1], 0x0104701F);
	EXPECT_EQ(aInts[2], 0x10188000);

	// long padding
	StrToInts(aInts, 4, "abc");
	EXPECT_EQ(aInts[0], 0xE1E2E380);
	EXPECT_EQ(aInts[1], 0x80808080);
	EXPECT_EQ(aInts[2], 0x80808080);
	EXPECT_EQ(aInts[3], 0x80808000);
}

TEST(Str, IntsToStr)
{
	int aInts[8];
	char aStr[sizeof(aInts)];

	aInts[0] = 0xE1808000;
	EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "a");

	aInts[0] = 0xE1E28000;
	EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "ab");

	aInts[0] = 0xE1E2E300;
	EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abc");

	aInts[0] = 0xE1E2E3E4;
	aInts[1] = 0x80808000;
	EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abcd");

	aInts[0] = 0xE1E2E3E4;
	aInts[1] = 0xE5808000;
	EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abcde");

	aInts[0] = 0xE1E2E3E4;
	aInts[1] = 0xE5E68000;
	EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abcdef");

	aInts[0] = 0xE1E2E3E4;
	aInts[1] = 0xE5E6E700;
	EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abcdefg");

	aInts[0] = 0x4336433C;
	aInts[1] = 0x43248000;
	EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "öüä");

	aInts[0] = 0xE14E3263;
	aInts[1] = 0x0104701F;
	aInts[2] = 0x10188000;
	EXPECT_TRUE(IntsToStr(aInts, 3, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "aβい🐘");

	// long padding
	aInts[0] = 0xE1E2E380;
	aInts[1] = 0x80808080;
	aInts[2] = 0x80808080;
	aInts[3] = 0x80808000;
	EXPECT_TRUE(IntsToStr(aInts, 4, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abc");

	// early null character (0x80)
	aInts[0] = 0xE1E2E380;
	aInts[1] = 0xE1E2E3E4;
	aInts[2] = 0xE1E2E3E4;
	aInts[3] = 0xE1E2E300;
	EXPECT_TRUE(IntsToStr(aInts, 4, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abc");

	// invalid UTF-8
	aInts[0] = 0xE17FE200;
	EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "");

	// invalid UTF-8 (0x00 in string data, which is translated to '\x80')
	aInts[0] = 0xE100E200;
	EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "");

	// invalid UTF-8
	aInts[0] = 0xE1E2E36F;
	aInts[1] = 0x3F40E4E5;
	aInts[2] = 0xE67FE700;
	EXPECT_FALSE(IntsToStr(aInts, 3, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "");

	// invalid UTF-8 and missing null-terminator
	aInts[0] = 0x7F7F7F7F;
	EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "");

	// missing null-terminator at the end is ignored
	aInts[0] = 0xE1E2E3E4;
	EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
	EXPECT_STREQ(aStr, "abc");

	// basic fuzzing: no input integer should result in invalid UTF-8 in the string
	for(int i = 0; i <= 0xFFFFFF; i += 7) // checking all values takes a bit too long
	{
		mem_zero(aStr, std::size(aStr));
		aInts[0] = 0xE1 << 24 | i;
		aInts[1] = i << 8 | 0xE2;
		aInts[2] = 0xE3 << 24 | i;
		const bool ConversionResult = IntsToStr(aInts, 3, aStr, std::size(aStr));
		// ensure null-termination before calling str_utf8_check
		ASSERT_TRUE(mem_has_null(aStr, std::size(aStr)));
		ASSERT_TRUE(str_utf8_check(aStr));
		ASSERT_TRUE(ConversionResult || aStr[0] == '\0');
	}
}

#if defined(CONF_FAMILY_WINDOWS)
TEST(Str, WindowsUtf8WideConversion)
{
	const char *apUtf8Strings[] = {
		"",
		"abc",
		"a bb ccc dddd       eeeee",
		"öüä",
		"привет Наташа",
		"ąçęįǫų",
		"DDNet最好了",
		"aβい🐘"};
	const wchar_t *apWideStrings[] = {
		L"",
		L"abc",
		L"a bb ccc dddd       eeeee",
		L"öüä",
		L"привет Наташа",
		L"ąçęįǫų",
		L"DDNet最好了",
		L"aβい🐘"};
	static_assert(std::size(apUtf8Strings) == std::size(apWideStrings));
	for(size_t i = 0; i < std::size(apUtf8Strings); i++)
	{
		const std::optional<std::string> ConvertedUtf8 = windows_wide_to_utf8(apWideStrings[i]);
		const std::wstring ConvertedWide = windows_utf8_to_wide(apUtf8Strings[i]);
		ASSERT_TRUE(ConvertedUtf8.has_value());
		EXPECT_STREQ(ConvertedUtf8.value().c_str(), apUtf8Strings[i]);
		EXPECT_STREQ(ConvertedWide.c_str(), apWideStrings[i]);
	}
}
#endif
