//
// overall testing of many of the RCString categories.
// has gets() statements to allow checks for dangling
// pointers every so often.
//
#import <stdio.h>
#import <stdlib.h>
#import <signal.h>
#import <fcntl.h>
#import <string.h>
#import <sys/time.h>
#import <RCString.h>

int igErrorCount = 0;

char *getrandomstring();

int
main(int c, char **v)
{
	void xp_test(char *);
	void insert_test(char *bpBaseString, char *bpToInsert, int index);
	void retrieve_test();
	void replace_test();
	char *bvLargeString;
	char buf[512];
	int icCount, iErrorCount, iInsertLocation;
	struct timeval sTimeVal;

#ifdef NeXT
	int signalHandler();

	// install a few signal handlers
	signal(SIGSEGV, signalHandler);
	signal(SIGBUS,  signalHandler);
	signal(SIGILL,  signalHandler);

	// turn on fairly high level of malloc error checking
	malloc_debug(8);
#endif NeXT

	if (c > 1)
		gets(buf);

	iErrorCount = igErrorCount;
	puts("\ntesting of explicity NULL strings");
	xp_test(NULL);
	if (iErrorCount != igErrorCount)
		printf("committed %d sins\n", igErrorCount - iErrorCount);
	if (c > 1)
		gets(buf);

	iErrorCount = igErrorCount;
	puts("\ntesting of zero length strings");
	xp_test("");
	if (iErrorCount != igErrorCount)
		printf("committed %d sins\n", igErrorCount - iErrorCount);
	if (c > 1)
		gets(buf);

	iErrorCount = igErrorCount;
	puts("\ntesting of 1-char length strings");
	xp_test("a");
	if (iErrorCount != igErrorCount)
		printf("committed %d sins\n", igErrorCount - iErrorCount);
	if (c > 1)
		gets(buf);

	iErrorCount = igErrorCount;
	puts("\ntesting of 2-char length strings");
	xp_test("aB");
	if (iErrorCount != igErrorCount)
		printf("committed %d sins\n", igErrorCount - iErrorCount);
	if (c > 1)
		gets(buf);

	iErrorCount = igErrorCount;
	puts("\ntesting of 8973-char length strings");
	bvLargeString = malloc(8974);
	for (icCount = 0; icCount < 8973; ++icCount)
		bvLargeString[icCount] = (icCount % 94) + ' ';
	xp_test(bvLargeString);
	if (iErrorCount != igErrorCount)
		printf("committed %d sins\n", igErrorCount - iErrorCount);
	if (c > 1)
		gets(buf);

	if (igErrorCount)
		printf("committed %d sins in allocate/free/empty/compare tests\n",
			igErrorCount);

	igErrorCount = 0;

	puts("\ntesting inserting null strings into other null strings");
	iErrorCount = igErrorCount;
	// insert_test(char *bpBaseString, char *bpToInsert, int index)
	insert_test("", "", 0);
	insert_test("", "", -1);
	insert_test("", "", 1);
	insert_test("", "", 2);
	if (iErrorCount != igErrorCount)
		printf("null insertion committed %d sins\n", igErrorCount - iErrorCount);

	puts("\ntesting inserting null strings into 1-char strings");
	iErrorCount = igErrorCount;
	// insert_test(char *bpBaseString, char *bpToInsert, int index)
	insert_test("", "a", 0);
	insert_test("", "a", -1);
	insert_test("", "a", 1);
	insert_test("B", "", -2);
	insert_test("B", "", -1);
	insert_test("B", "", 0);
	insert_test("B", "", 1);
	insert_test("B", "", 2);
	if (iErrorCount != igErrorCount)
		printf("insertion committed %d sins\n", igErrorCount - iErrorCount);

	puts("\ntesting inserting 1-char strings into 1-char strings");
	iErrorCount = igErrorCount;
	// insert_test(char *bpBaseString, char *bpToInsert, int index)
	insert_test("0", "a", -1);
	insert_test("0", "a", 0);
	insert_test("0", "a", 1);
	insert_test("B", "7", -2);
	insert_test("B", "7", -1);
	insert_test("B", "7", 0);
	insert_test("B", "7", 1);
	insert_test("B", "7", 2);
	if (iErrorCount != igErrorCount)
		printf("insertion committed %d sins\n", igErrorCount - iErrorCount);

	puts("\ntesting inserting 1-char strings into 2-char strings");
	iErrorCount = igErrorCount;
	// insert_test(char *bpBaseString, char *bpToInsert, int index)
	insert_test("00", "a", -2);
	insert_test("00", "a", -1);
	insert_test("00", "a", 0);
	insert_test("00", "a", 1);
	insert_test("00", "a", 2);
	insert_test("B", "7F", -2);
	insert_test("B", "7F", -1);
	insert_test("B", "7F", 0);
	insert_test("B", "7F", 1);
	insert_test("B", "7F", 2);
	if (iErrorCount != igErrorCount)
		printf("insertion committed %d sins\n", igErrorCount - iErrorCount);

	// redo the "large" string
	for (icCount = 0; icCount < 8973; ++icCount)
		bvLargeString[icCount] = (icCount % 94) + ' ';

	puts("\ntesting inserting large strings into large strings");
	iErrorCount = igErrorCount;
	insert_test(bvLargeString, bvLargeString, -1);
	insert_test(bvLargeString, bvLargeString, 0);
	insert_test(bvLargeString, bvLargeString, 1);
	insert_test(bvLargeString, bvLargeString, -2);
	insert_test(bvLargeString, bvLargeString, -1);
	insert_test(bvLargeString, bvLargeString, 0);
	insert_test(bvLargeString, bvLargeString, 1);
	insert_test(bvLargeString, bvLargeString, 2);

	for (icCount = 0; icCount < 5; ++icCount) {
		gettimeofday(&sTimeVal, NULL);
		iInsertLocation = ((sTimeVal.tv_sec % sTimeVal.tv_usec) % 8973);
		printf("inserting a large string at %d\n", iInsertLocation);
		insert_test(bvLargeString, bvLargeString, iInsertLocation);
	}
	if (iErrorCount != igErrorCount)
		printf("insertion committed %d sins\n", igErrorCount - iErrorCount);

	puts("\ntesting replacement methods");
	iErrorCount = igErrorCount;
	replace_test();
	if (iErrorCount != igErrorCount)
		printf("replacement committed %d sins\n", igErrorCount - iErrorCount);

	if (c > 1)
		gets(buf);

	puts("\ntesting retrieval methods");
	iErrorCount = igErrorCount;
	retrieve_test();
	if (iErrorCount != igErrorCount)
		printf("retrieval committed %d sins\n", igErrorCount - iErrorCount);

	printf("committed %d total sins\n", igErrorCount);

	if (c > 1)
		gets(buf);

	return(0);
}

char *method_strings[3] = { "-new           ", "-newFromString:",
	"-newFromObject:" };

//
//  allocation, deletion, emptying and comparison tests.
//
void
xp_test(char *bpTestValue)
{
	int i, j;

	RCString *oString1, *oString2, *oString3;
	RCString *ovString[3];
	RCString *ovCopies[3];

	puts("testing of -new methods");
	oString1 = [RCString new];
	oString2 = [RCString newFromString:bpTestValue];
	oString3 = [RCString newFromObject:oString2];
	[oString3 copyReference];

	if (!oString1) {
		fputs(" -new method failed\n", stderr);
		++igErrorCount;
	}
	if (!oString2) {
		fputs(" -newFromString method failed\n", stderr);
		++igErrorCount;
	}
	if (!oString3) {
		++igErrorCount;
		fputs(" -newFromObject method failed\n", stderr);
	}

	[oString1 free];
	[oString2 free];
	[oString3 free];

	// reference copy counting
	puts("\ntesting of reference counting");
	ovString[0] = [RCString new];
	ovString[1] = [RCString newFromString:bpTestValue];
	ovString[2] = [RCString newFromObject:ovString[1]];
	[ovString[2] copyReference];

	for (j = 0; j < 3; ++j) {
		if ([ovString[j] references] != 1) {
			printf("%s created object: %d refs (should be 1), internal rep 0x%x\n",
				method_strings[j], [ovString[j] references], (unsigned int)[ovString[j] internal]);
			++igErrorCount;
		}
	}

	puts("\ncalling copyReference");
	for (i = 0; i < 5; ++i) {
		for (j = 0; j < 3; ++j) {
			[ovString[j] copyReference];
		}
	}

	for (j = 0; j < 3; ++j) {
		if ([ovString[j] references] != 1) {
			printf("%s created object: %d refs (should be 1), internal rep 0x%x\n",
				method_strings[j], [ovString[j] references], (unsigned int)[ovString[j] internal]);
			++igErrorCount;
		}
	}

	puts("\ncalling newFromObject: on objects that ought have 1 refs");
	for (j = 0; j < 3; ++j)
		ovCopies [j] = [RCString newFromObject:ovString[j]];

	for (j = 0; j < 3; ++j) {
		if ([ovString[j] references] != 2 || [ovCopies[j] references] != 2) {
			printf(
			"%s created object: %d refs (should be 2), internal rep 0x%x\n",
			method_strings[j], [ovString[j] references],
			(unsigned int)[ovString[j] internal]);
			printf(
			"copied object: %d refs (should be 2), internal rep 0x%x\n",
			[ovCopies[j] references], (unsigned int)[ovCopies[j] internal]);
			++igErrorCount;
		}
	}

	puts("\ncalling copyReference on object copies");
	for (j = 0; j < 3; ++j)
		[ovCopies [j] copyReference];

	for (j = 0; j < 3; ++j) {
		if ([ovString[j] references] != 1 || [ovCopies[j] references] != 1) {
		printf("%s created object: %d refs (should be 1), internal rep 0x%x\n",
			method_strings[j], [ovString[j] references],
			(unsigned int)[ovString[j] internal]);
			printf(
			"copied object: %d refs (should be 1), internal rep 0x%x\n",
			[ovCopies[j] references], (unsigned int)[ovCopies[j] internal]);

			++igErrorCount;
		}
	}

	for (j = 0; j < 3; ++j) {
		[ovString[j] free];
		[ovCopies[j] free];
	}

	puts("testing of -empty methods");
	ovString[0] = [RCString new];
	ovString[1]= [RCString newFromString:bpTestValue];
	ovString[2] = [RCString newFromObject:ovString[1]];
	[ovString[2] copyReference];
	for (j = 0; j < 3; ++j)
		[ovString[j] empty];
	for (j = 0; j < 3; ++j) {
		if ([ovString[j] length] != 0) {
			fprintf(stderr, 
		"%s created object: %d refs (should be 1), internal rep 0x%x, length NOT 0 after -empty (%u)\n",
			method_strings[j], [ovString[j] references], (unsigned int)[ovString[j] internal], [ovString[j] length]);
			++igErrorCount;
		}
		[ovString[j] free];
	}

	puts("testing of -toUpper method");
	ovString[0] = [RCString new];
	ovString[1]= [RCString newFromString:bpTestValue];
	ovString[2] = [RCString newFromObject:ovString[1]];
	[ovString[2] copyReference];
	for (j = 0; j < 3; ++j)
		[ovString[j] toUpper];
	for (j = 0; j < 3; ++j) {
		int icIndex, iLength = [ovString[j] length];
		char *bpData = [ovString[j] data];
		for (icIndex = 0; icIndex < iLength; ++icIndex) {
			if (bpData[icIndex] >= 'a' && bpData[icIndex] <= 'z') {
				printf(
"%s created object: %d refs (should be 1), internal rep 0x%x\n",
method_strings[j], [ovString[j] references], (unsigned int)[ovString[j] internal]);
				printf(
"\tProblem with character \'%c\', at index %d, lowercase when it shouldn't be\n",
					bpData[icIndex], icIndex);
				++igErrorCount;
				break;
			}
		}
		[ovString[j] free];
	}

	puts("testing of -toLower method");
	ovString[0] = [RCString new];
	ovString[1]= [RCString newFromString:bpTestValue];
	ovString[2] = [RCString newFromObject:ovString[1]];
	[ovString[2] copyReference];
	for (j = 0; j < 3; ++j)
		[ovString[j] toLower];
	for (j = 0; j < 3; ++j) {
		int icIndex, iLength = [ovString[j] length];
		char *bpData = [ovString[j] data];
		for (icIndex = 0; icIndex < iLength; ++icIndex) {
			if (bpData[icIndex] >= 'A' && bpData[icIndex] <= 'Z') {
				printf(
"%s created object: %d refs (should be 1), internal rep 0x%x\n",
method_strings[j], [ovString[j] references], (unsigned int)[ovString[j] internal]);
				printf(
"\tProblem with character \'%c\', at index %d, uppercase when it shouldn't be\n",
					bpData[icIndex], icIndex);
				++igErrorCount;
				break;
			}
		}
		[ovString[j] free];
	}


	puts("testing of case-sensitive -compare methods");
	oString1 = [RCString newFromString:bpTestValue];
	oString2 = [RCString newFromObject:oString1];

	if ([oString1 length] && [oString2 length]
		&& [oString1 compareWithObject:oString2]) {
		puts("special case (same internal rep) comparison.  Should match, but it doesn't");
		printf("internal reps at 0x%x and 0x%x\n", (unsigned int)[oString1 internal], 
			(unsigned int)[oString2 internal]);
		++igErrorCount;
	}
	if ([oString1 length] && strlen(bpTestValue) > 0
		&& [oString1 compareWithString:bpTestValue]) {
		puts("special case (same ASCIIZ string) comparison.  Should match, but it doesn't");
		printf("ASCIIZ string: \"%s\", object's string: \"%s\"\n",
			bpTestValue?bpTestValue:"NULL", [oString1 data]);
		++igErrorCount;
	}

	// trigger up an object that is guaranteed not to match
	// _if_ case is considered.
	{
		int yUpperCase = 0;
		int icCount;
		char *bpData = [oString2 data];
		for (icCount = 0; icCount < [oString2 length]; ++icCount)
			if (bpData[icCount] >= 'A' && bpData[icCount] <= 'Z') {
				yUpperCase = 1;  // there is an uppercase character
				break;
			}

		// these should both force copy-on-write
		if (yUpperCase)
			[oString2 toLower];
		else
			[oString2 toUpper];
	}

	if ([oString1 length] && [oString2 length] 
		&& ![oString1 compareWithObject:oString2]) {
		puts("Different internal rep comparison.  Shouldn't match, but it does");
		printf("internal reps at 0x%x and 0x%x\n", (unsigned int)[oString1 internal], 
			(unsigned int)[oString2 internal]);
		printf("object 1's string: \"%s\", object 2's string: \"%s\"\n",
			[oString1 data], [oString2 data]);
		++igErrorCount;
	}
	if ([oString1 length] && strlen(bpTestValue) > 0
		&& ![oString2  compareWithString:bpTestValue]) {
		puts("Comparison to ASCIIZ string.  Shouldn't match, but it does");
		printf("object's string: \"%s\", ASCIIZ string: \"%s\"\n",
			[oString2 data], bpTestValue);
		++igErrorCount;
	}

	puts("testing of case-insensitive -compare methods");
	[oString1 caseSensitive:NO];
	[oString2 caseSensitive:NO];
	oString3 = [RCString newFromObject:oString1];

	if ([oString1 compareWithObject:oString3]) {
		puts("special case (same internal rep) comparison.  Should match, but it doesn't");
		printf("internal reps at 0x%x and 0x%x\n", (unsigned int)[oString1 internal], 
			(unsigned int)[oString2 internal]);
		++igErrorCount;
	}
	if ([oString1  compareWithString:bpTestValue]) {
		puts("Comparison to ASCIIZ string.  Should match, but it doesn't");
		printf("object's string: \"%s\", ASCIIZ string: \"%s\"\n",
			[oString2 data], bpTestValue);
		++igErrorCount;
	}
	if ([oString1 compareWithObject:oString2]) {
		puts("Different internal rep comparison.  Should match, but it doesn't");
		printf("internal reps at 0x%x and 0x%x\n", (unsigned int)[oString1 internal], 
			(unsigned int)[oString2 internal]);
		printf("object 1's string: \"%s\", object 2's string: \"%s\"\n",
			[oString1 data], [oString2 data]);
		++igErrorCount;
	}
	if ([oString2  compareWithString:bpTestValue]) {
		puts("Comparison to ASCIIZ string.  Should match, but it doesn't");
		printf("object's string: \"%s\", ASCIIZ string: \"%s\"\n",
			[oString2 data], bpTestValue);
		++igErrorCount;
	}

	[oString1 free];
	[oString2 free];
	[oString3 free];

}

void
insert_test(char *bpBaseString, char *bpToInsert, int index)
{
	RCString       *oStringAppend;
	RCString       *oStringPrepend;
	RCString       *oStringMiddle;
	char *bpTmpBuffer;
	int icCnt, iSize;

	iSize = strlen(bpBaseString) + strlen(bpToInsert) + 1;
	bpTmpBuffer = malloc(iSize);
	bzero(bpTmpBuffer, iSize);

	oStringAppend = [RCString newFromString:bpBaseString];
	[oStringAppend appendString:bpToInsert];
	strcpy(bpTmpBuffer, bpBaseString);
	strcat(bpTmpBuffer, bpToInsert);
	if (strcmp(bpTmpBuffer, [oStringAppend data])) {
		++igErrorCount;
		printf("tried to append \"%s\" to \"%s\": should be \"%s\", is \"%s\"\n",
			bpToInsert, bpBaseString, bpTmpBuffer, [oStringAppend data]);
	}

	bzero(bpTmpBuffer, iSize);

	oStringPrepend = [RCString newFromString:bpBaseString];
	[oStringPrepend prependString:bpToInsert];
	strcpy(bpTmpBuffer, bpToInsert);
	strcat(bpTmpBuffer, bpBaseString);
	if (strcmp(bpTmpBuffer, [oStringPrepend data])) {
		++igErrorCount;
		printf(
			"tried to prepend \"%s\" to \"%s\": should be \"%s\", is \"%s\"\n",
			bpToInsert, bpBaseString, bpTmpBuffer, [oStringPrepend data]);
	}

	bzero(bpTmpBuffer, iSize);

	oStringMiddle = [RCString newFromString:bpBaseString];
	[oStringMiddle insertString:bpToInsert at:index];

	if (index >= 0) {
		if (index > strlen(bpBaseString))
			index = strlen(bpBaseString);
		for (icCnt = 0; icCnt < index; ++icCnt)
			bpTmpBuffer[icCnt] = bpBaseString[icCnt];
		strcat(&bpTmpBuffer[index], bpToInsert);
		if (index < strlen(bpBaseString))
			strcat(bpTmpBuffer, &bpBaseString[index]);
	} else {
		strcat(bpTmpBuffer, bpBaseString);
	}

	if (strcmp(bpTmpBuffer, [oStringMiddle data])) {
		++igErrorCount;
		printf("tried to stick \"%s\" in \"%s\": should be \"%s\", is \"%s\"\n",
			bpToInsert, bpBaseString, bpTmpBuffer, [oStringMiddle data]);
	}

	[oStringAppend free];
	[oStringPrepend free];
	[oStringMiddle free];
	free(bpTmpBuffer);
}

void
replace_test()
{
	RCString *o1, *o2;
	struct srep *sTmp;
	char *bpOldString, *bpNewString;

	o1 = [RCString new];
	sTmp = [o1 internal];
	[o1 replaceWithAsciiString:NULL];
	if (sTmp != [o1 internal]) {
		if (strlen([o1 data]) != 0) {
			puts("replacing (empty string) object's internal string with a NULL");
			++igErrorCount;
		}
	}
	[o1 free];

	bpOldString = getrandomstring();
	o1 = [RCString newFromString:bpOldString];
	sTmp = [o1 internal];
	bpNewString = getrandomstring();
	[o1 replaceWithAsciiString:bpNewString];
	if (sTmp != [o1 internal]) {
		if (![o1 compareWithString:bpOldString]) {
			puts("replacing object's internal string with a another string");
			++igErrorCount;
		}
	}
	[o1 free];
	free(bpOldString);
	free(bpNewString);


	bpOldString = getrandomstring();
	o1 = [RCString newFromString:bpOldString];
	o2 = [RCString newFromObject:o1];
	bpNewString = getrandomstring();
	[o2 replaceWithAsciiString:bpNewString];

	if ([o1 internal] == [o2 internal]) {
		puts("replaceWithAsciiString: failure");
		++igErrorCount;
	}

	[o1 replaceWithObject:o2];
	if ([o1 internal] != [o2 internal]) {
		puts("replaceWithObject: failure");
		++igErrorCount;
	}

	[o1 free];
	[o2 free];
	free(bpOldString);
	free(bpNewString);

	return;
}

void
retrieve_test()
{
	char *bpTestString = getrandomstring();
	RCString *o1 = [RCString newFromString:bpTestString];

	if (o1) {
		char *bpSubString;
		RCString *oSubObject;
		int iLength, iMaxLength;

		// check entire string
		iMaxLength = strlen(bpTestString);
		bpSubString = [o1 subStringAt:0 extent:iMaxLength];
		if (strcmp(bpSubString, bpTestString)) {
			puts("comparison of entire string from subStringAt: method failed");
			++igErrorCount;
		}
		oSubObject = [o1 subObjectAt:0 extent:iMaxLength];
		if (oSubObject) {
			if ([o1 compareWithObject:oSubObject]
				|| [oSubObject compareWithObject:o1]) {
				puts("comparison of entire object with subobject failed");
				++igErrorCount;
			}
			[oSubObject free];
		} else {
			puts("subObjectAt:extent: method didn't produce an object");
			++igErrorCount;
		}
		if (bpSubString) free(bpSubString);

		// check all substrings, beginning at start of string
		for (iLength = 1; iLength < iMaxLength; iLength += 10) {
			bpSubString = [o1 subStringAt:0 extent:iLength];
			if (strncmp(bpSubString, bpTestString, iLength)) {
				printf("substring of length %d, beginning at start of string doesn't match\n", iLength);
				free(bpSubString);
				++igErrorCount;
				break;
			}
			oSubObject = [o1 subObjectAt:0 extent:iLength];
			if (oSubObject) {
				if (strncmp([o1 data], [oSubObject data], iLength)) {
					puts("comparison of entire object with subobject failed");
					printf("comparison of object with subobject for length %d failed", iLength);
					++igErrorCount;
				}
				[oSubObject free];
			} else {
				printf("subObjectAt:extent: method didn't produce an object for length %d", iLength);
				++igErrorCount;
			}
			free(bpSubString);
		}
		// check all substrings, from end of string
		for (iLength = iMaxLength; iLength > 1; iLength -= 10) {
			bpSubString = [o1 subStringAt:(iMaxLength - iLength) extent:iLength];
			if (strncmp(bpSubString, &bpTestString[iMaxLength - iLength], iLength)) {
				printf("substring of length %d, beginning at start of string doesn't match\n", iLength);
				free(bpSubString);
				++igErrorCount;
				break;
			}
			oSubObject = [o1 subObjectAt:(iMaxLength - iLength) extent:iLength];
			if (oSubObject) {
				if (strncmp([oSubObject data],
					&([o1 data][iMaxLength - iLength]), iLength)) {
					printf("comparison of object with subobject for length %d failed\n", iLength);
					++igErrorCount;
				}
				[oSubObject free];
			} else {
				printf("subObjectAt:extent: method didn't produce an object for length %d\n", iLength);
				++igErrorCount;
			}
			free(bpSubString);
		}


		[o1 free];
	}

	free(bpTestString);
}

#ifdef NeXT
int
signalHandler(int something)
{
	printf("signalHandler(%d) (0x%x)\n", something, (unsigned int)something);
	exit(0);
}
#endif

char *
getrandomstring()
{
	struct timeval sTimeVal;
	int iLength, iBegin, icIndex;
	char *bpRandStr;

	gettimeofday(&sTimeVal, NULL);
	iLength = ((sTimeVal.tv_sec % sTimeVal.tv_usec) % 8192);
	iBegin = (sTimeVal.tv_sec + sTimeVal.tv_usec)%26;

	bpRandStr = malloc(iLength + 1);
	if (bpRandStr) {
		for (icIndex = 0; icIndex < iLength; ++icIndex)
			bpRandStr[icIndex] = (iBegin++ % 0x5f) + ' ';
		bpRandStr[iLength] = '\0';
	}

	return bpRandStr;
}
