#include "utilities/String.h"
#include "unittest/TestSuite.h"
#include <limits.h>

static void testSTR(void) {
	const char * hello = "hello";
	String string = STR(hello);
	TestCase_assertPointerNonNULL(string.bytes);
	TestCase_assertStringPointerEqual(string.bytes, hello);
	TestCase_assertSizeEqual(string.length, 5);
	TestCase_assertEnumEqual(string.encoding, ENCODING_UTF8);
	
	string = STR("test");
	TestCase_assertSizeEqual(string.length, 4);
	TestCase_assertStringEqual(string.bytes, "test");
	TestCase_assertEnumEqual(string.encoding, ENCODING_UTF8);
	
	string = STRL("literal");
	TestCase_assertSizeEqual(string.length, 7);
	TestCase_assertStringEqual(string.bytes, "literal");
	TestCase_assertEnumEqual(string.encoding, ENCODING_UTF8);
	
	string = STRL("literal2");
	TestCase_assertSizeEqual(string.length, 8);
	TestCase_assertStringEqual(string.bytes, "literal2");
	TestCase_assertEnumEqual(string.encoding, ENCODING_UTF8);
}

static void testCopy(void) {
	String string = {"abcd", 4, ENCODING_UTF8};
	String copy = String_copy(string);
	TestCase_assertPointerNonNULL(copy.bytes);
	TestCase_assertPointerUnequal(copy.bytes, string.bytes);
	TestCase_assertSizeEqual(copy.length, 4);
	TestCase_assertStringEqual(copy.bytes, "abcd");
	TestCase_assertEnumEqual(copy.encoding, ENCODING_UTF8);
	String_free(copy);
	
	uint16_t stringUTF16[] = {0x0061, 0x00A3, 0x4E09, 0xD834, 0xDD10, 0x007A, 0x0000};
	string.bytes = stringUTF16;
	string.length = 6;
	string.encoding = ENCODING_UTF16;
	copy = String_copy(string);
	TestCase_assertPointerNonNULL(copy.bytes);
	TestCase_assertPointerUnequal(copy.bytes, string.bytes);
	TestCase_assertSizeEqual(copy.length, 6);
	TestCase_assertBytesEqual(copy.bytes, stringUTF16, sizeof(stringUTF16));
	TestCase_assertEnumEqual(copy.encoding, ENCODING_UTF16);
	String_free(copy);
	
	uint32_t stringUTF32[] = {0x00000061, 0x000000A3, 0x00004E09, 0x0001D110, 0x0000007A, 0x00000000};
	string.bytes = stringUTF32;
	string.length = 5;
	string.encoding = ENCODING_UTF32;
	copy = String_copy(string);
	TestCase_assertPointerNonNULL(copy.bytes);
	TestCase_assertPointerUnequal(copy.bytes, string.bytes);
	TestCase_assertSizeEqual(copy.length, 5);
	TestCase_assertBytesEqual(copy.bytes, stringUTF32, sizeof(stringUTF32));
	TestCase_assertEnumEqual(copy.encoding, ENCODING_UTF32);
	String_free(copy);
	
	unsigned int stringCharacterIndex[] = {0, 1, 2, 3, UINT_MAX};
	string.bytes = stringCharacterIndex;
	string.length = 4;
	string.encoding = ENCODING_CHARACTER_INDEX;
	copy = String_copy(string);
	TestCase_assertPointerNonNULL(copy.bytes);
	TestCase_assertPointerUnequal(copy.bytes, string.bytes);
	TestCase_assertSizeEqual(copy.length, 4);
	TestCase_assertBytesEqual(copy.bytes, stringCharacterIndex, sizeof(stringCharacterIndex));
	TestCase_assertEnumEqual(copy.encoding, ENCODING_CHARACTER_INDEX);
	String_free(copy);
}

static void testSubstring(void) {
	String string = {"Test string", 11, ENCODING_UTF8};
	String substring = String_substring(string, 4, 3);
	TestCase_assertPointerEqual(substring.bytes, string.bytes + 4);
	TestCase_assertSizeEqual(substring.length, 3);
	TestCase_assertEnumEqual(substring.encoding, ENCODING_UTF8);
	
	uint16_t stringUTF16[] = {0x0061, 0x00A3, 0x4E09, 0xD834, 0xDD10, 0x007A, 0x0000};
	string.bytes = stringUTF16;
	string.length = 6;
	string.encoding = ENCODING_UTF16;
	substring = String_substring(string, 1, 4);
	TestCase_assertPointerEqual(substring.bytes, string.bytes + 2);
	TestCase_assertSizeEqual(substring.length, 4);
	TestCase_assertEnumEqual(substring.encoding, ENCODING_UTF16);
	
	uint32_t stringUTF32[] = {0x00000061, 0x000000A3, 0x00004E09, 0x0001D110, 0x0000007A, 0x00000000};
	string.bytes = stringUTF32;
	string.length = 5;
	string.encoding = ENCODING_UTF32;
	substring = String_substring(string, 2, 2);
	TestCase_assertPointerEqual(substring.bytes, string.bytes + 8);
	TestCase_assertSizeEqual(substring.length, 2);
	TestCase_assertEnumEqual(substring.encoding, ENCODING_UTF32);
	
	unsigned int stringCharacterIndex[] = {0, 1, 2, 3, UINT_MAX};
	string.bytes = stringCharacterIndex;
	string.length = 4;
	string.encoding = ENCODING_CHARACTER_INDEX;
	substring = String_substring(string, 1, 3);
	TestCase_assertPointerEqual(substring.bytes, string.bytes + sizeof(unsigned int));
	TestCase_assertSizeEqual(substring.length, 3);
	TestCase_assertEnumEqual(substring.encoding, ENCODING_CHARACTER_INDEX);
}

static void testTranscode(void) {
	uint8_t stringUTF8[] = {0x61, 0xC2, 0xA3, 0xE4, 0xB8, 0x89, 0xF0, 0x9D, 0x84, 0x90, 0x7A, 0x00};
	uint16_t stringUTF16[] = {0x0061, 0x00A3, 0x4E09, 0xD834, 0xDD10, 0x007A, 0x0000};
	uint32_t stringUTF32[] = {0x00000061, 0x000000A3, 0x00004E09, 0x0001D110, 0x0000007A, 0x00000000};
	
	String string;
	string.bytes = stringUTF8;
	string.length = 11;
	string.encoding = ENCODING_UTF8;
	String result = String_transcode(string, ENCODING_UTF16);
	TestCase_assertBytesEqual(result.bytes, stringUTF16, sizeof(stringUTF16));
	TestCase_assertSizeEqual(result.length, 6);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF16);
	String_free(result);
	
	result = String_transcode(string, ENCODING_UTF32);
	TestCase_assertBytesEqual(result.bytes, stringUTF32, sizeof(stringUTF32));
	TestCase_assertSizeEqual(result.length, 5);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF32);
	String_free(result);
	
	string.bytes = stringUTF16;
	string.length = 6;
	string.encoding = ENCODING_UTF16;
	result = String_transcode(string, ENCODING_UTF8);
	TestCase_assertBytesEqual(result.bytes, stringUTF8, sizeof(stringUTF8));
	TestCase_assertSizeEqual(result.length, 11);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF8);
	String_free(result);
	
	result = String_transcode(string, ENCODING_UTF32);
	TestCase_assertBytesEqual(result.bytes, stringUTF32, sizeof(stringUTF32));
	TestCase_assertSizeEqual(result.length, 5);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF32);
	String_free(result);
	
	string.bytes = stringUTF32;
	string.length = 5;
	string.encoding = ENCODING_UTF32;
	result = String_transcode(string, ENCODING_UTF8);
	TestCase_assertBytesEqual(result.bytes, stringUTF8, sizeof(stringUTF8));
	TestCase_assertSizeEqual(result.length, 11);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF8);
	String_free(result);
	
	result = String_transcode(string, ENCODING_UTF16);
	TestCase_assertBytesEqual(result.bytes, stringUTF16, sizeof(stringUTF16));
	TestCase_assertSizeEqual(result.length, 6);
	TestCase_assertEnumEqual(result.encoding, ENCODING_UTF16);
	String_free(result);
}

static void testIsEqual(void) {
	TestCase_assertBoolTrue(String_isEqual(STRL("a"), STRL("a")));
	TestCase_assertBoolFalse(String_isEqual(STRL("a"), STRL("b")));
	TestCase_assertBoolFalse(String_isEqual(STRL("a"), STRL("aa")));
	TestCase_assertBoolFalse(String_isEqual(STRL("aa"), STRL("a")));
	
	String string1, string2;
	uint8_t string1UTF8[] = {0x41, 0x42};
	uint8_t string2UTF8[] = {0x41, 0x42};
	string1 = STRING(string1UTF8, 2, ENCODING_UTF8);
	string2 = STRING(string2UTF8, 2, ENCODING_UTF8);
	TestCase_assertBoolTrue(String_isEqual(string1, string2));
	string2UTF8[1] = 0x43;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
	string2UTF8[1] = 0x42;
	string2.length = 1;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
	string2.length = 2;
	string2.encoding = ENCODING_UTF16;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
	
	uint16_t string1UTF16[] = {0x0043, 0x0044};
	uint16_t string2UTF16[] = {0x0043, 0x0044};
	string1 = STRING(string1UTF16, 2, ENCODING_UTF16);
	string2 = STRING(string2UTF16, 2, ENCODING_UTF16);
	TestCase_assertBoolTrue(String_isEqual(string1, string2));
	string1UTF16[1] = 0x0045;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
	
	uint32_t string1UTF32[] = {0x00000045, 0x00000046};
	uint32_t string2UTF32[] = {0x00000045, 0x00000046};
	string1 = STRING(string1UTF32, 2, ENCODING_UTF32);
	string2 = STRING(string2UTF32, 2, ENCODING_UTF32);
	TestCase_assertBoolTrue(String_isEqual(string1, string2));
	string1UTF32[1] = 0x00000047;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
	
	unsigned int string1CharacterIndex[] = {0, 1};
	unsigned int string2CharacterIndex[] = {0, 1};
	string1 = STRING(string1CharacterIndex, 2, ENCODING_CHARACTER_INDEX);
	string2 = STRING(string2CharacterIndex, 2, ENCODING_CHARACTER_INDEX);
	TestCase_assertBoolTrue(String_isEqual(string1, string2));
	string1CharacterIndex[1] = 2;
	TestCase_assertBoolFalse(String_isEqual(string1, string2));
}

static void testStrncpyUTF8(void) {
	uint8_t stringUTF8[] = {0x61, 0xC2, 0xA3, 0xE4, 0xB8, 0x89, 0xF0, 0x9D, 0x84, 0x90, 0x7A, 0x00};
	uint16_t stringUTF16[] = {0x0061, 0x00A3, 0x4E09, 0xD834, 0xDD10, 0x007A, 0x0000};
	uint32_t stringUTF32[] = {0x00000061, 0x000000A3, 0x00004E09, 0x0001D110, 0x0000007A, 0x00000000};
	char result[12];
	char expectedResult[] = {0x61, 0xC2, 0xA3, 0xE4, 0xB8, 0x89, 0xF0, 0x9D, 0x84, 0x90, 0x7A, 0x00};
	char expectedResultTruncated[] = {0x61, 0xC2, 0xA3, 0x00};
	
	String string;
	string.bytes = stringUTF8;
	string.length = 11;
	string.encoding = ENCODING_UTF8;
	memset(result, 0, sizeof(result));
	String_strncpy_utf8(result, string, sizeof(result));
	TestCase_assertStringEqual(result, expectedResult);
	
	string.length = 3;
	memset(result, 0, sizeof(result));
	String_strncpy_utf8(result, string, sizeof(result));
	TestCase_assertStringEqual(result, expectedResultTruncated);
	
	string.bytes = stringUTF16;
	string.length = 6;
	string.encoding = ENCODING_UTF16;
	memset(result, 0, sizeof(result));
	String_strncpy_utf8(result, string, sizeof(result));
	TestCase_assertStringEqual(result, expectedResult);
	
	string.bytes = stringUTF32;
	string.length = 5;
	string.encoding = ENCODING_UTF32;
	memset(result, 0, sizeof(result));
	String_strncpy_utf8(result, string, sizeof(result));
	TestCase_assertStringEqual(result, expectedResult);
}

static void testStrcmpUTF8(void) {
	uint8_t stringUTF8[] = {0x61, 0xC2, 0xA3, 0xE4, 0xB8, 0x89, 0xF0, 0x9D, 0x84, 0x90, 0x7A, 0x00};
	uint16_t stringUTF16[] = {0x0061, 0x00A3, 0x4E09, 0xD834, 0xDD10, 0x007A, 0x0000};
	uint32_t stringUTF32[] = {0x00000061, 0x000000A3, 0x00004E09, 0x0001D110, 0x0000007A, 0x00000000};
	char less[2] = {0x60, 0x00};
	char more[2] = {0x62, 0x00};
	
	String string;
	string.bytes = stringUTF8;
	string.length = 11;
	string.encoding = ENCODING_UTF8;
	TestCase_assertIntEqual(String_strcmp_utf8(string, (const char *) stringUTF8), 0);
	TestCase_assertIntEqual(String_strcmp_utf8(string, less), 1);
	TestCase_assertIntEqual(String_strcmp_utf8(string, more), -1);
	
	string.bytes = stringUTF16;
	string.length = 6;
	string.encoding = ENCODING_UTF16;
	TestCase_assertIntEqual(String_strcmp_utf8(string, (const char *) stringUTF8), 0);
	TestCase_assertIntEqual(String_strcmp_utf8(string, less), 1);
	TestCase_assertIntEqual(String_strcmp_utf8(string, more), -1);
	
	string.bytes = stringUTF32;
	string.length = 5;
	string.encoding = ENCODING_UTF32;
	TestCase_assertIntEqual(String_strcmp_utf8(string, (const char *) stringUTF8), 0);
	TestCase_assertIntEqual(String_strcmp_utf8(string, less), 1);
	TestCase_assertIntEqual(String_strcmp_utf8(string, more), -1);
	
	string.bytes = (uint8_t *) "abcd";
	string.length = 4;
	string.encoding = ENCODING_UTF8;
	TestCase_assertIntEqual(String_strcmp_utf8(string, "abcde"), -1);
	int result = String_strcmp_utf8(string, "abc");
	TestCase_assert(result > 0, "Expected > 0 but got %d", result);
}

TEST_SUITE(StringTest,
           testSTR,
           testCopy,
           testSubstring,
           testTranscode,
           testIsEqual,
           testStrncpyUTF8,
           testStrcmpUTF8)
