#include "utilities/UndoUtilities.h"
#include "unittest/TestSuite.h"
#include "stem_core.h"

enum itemChangeType {
	CHANGE_TYPE_ADD,
	CHANGE_TYPE_REMOVE,
	CHANGE_TYPE_MODIFY
};

struct itemChange {
	enum itemChangeType changeType;
	unsigned int index;
	int item;
	unsigned int compareResult;
};

struct enumerateChangesContextStruct {
	unsigned int changeCount;
	struct itemChange * changes;
};

static unsigned int compare(void * oldItemUntyped, void * newItemUntyped, void * context) {
	int * oldItem = oldItemUntyped, * newItem = newItemUntyped;
	if (*newItem < *oldItem) {
		return 1;
	}
	if (*newItem > *oldItem) {
		return 2;
	}
	return 0;
}

static void addChange(struct enumerateChangesContextStruct * contextStruct, enum itemChangeType changeType, unsigned int index, int item, unsigned int compareResult) {
	contextStruct->changes = realloc(contextStruct->changes, (contextStruct->changeCount + 1) * sizeof(*contextStruct->changes));
	contextStruct->changes[contextStruct->changeCount].changeType = changeType;
	contextStruct->changes[contextStruct->changeCount].index = index;
	contextStruct->changes[contextStruct->changeCount].item = item;
	contextStruct->changes[contextStruct->changeCount].compareResult = compareResult;
	contextStruct->changeCount++;
}

static void itemAdded(unsigned int index, void * newItem, void * context) {
	addChange(context, CHANGE_TYPE_ADD, index, *(int *) newItem, 3);
}

static void itemRemoved(unsigned int index, void * oldItem, void * context) {
	addChange(context, CHANGE_TYPE_REMOVE, index, *(int *) oldItem, 3);
}

static void itemModified(unsigned int index, void * oldItem, void * newItem, unsigned int compareResult, void * context) {
	addChange(context, CHANGE_TYPE_MODIFY, index, *(int *) newItem ^ *(int *) oldItem, compareResult);
}

#define listArgs(number) oldList##number, sizeof(oldList##number) / sizeof(oldList##number[0]), newList##number, sizeof(newList##number) / sizeof(newList##number[0]), sizeof(oldList##number[0])

static void testEnumerateChangesBetweenOldAndNewLists(void) {
	struct enumerateChangesContextStruct contextStruct = {0, NULL};
	int oldList1[] = {1, 2, 3};
	int newList1[] = {1, 2, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(1), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 0);
	
	int oldList2[] = {1, 2, 3};
	int newList2[] = {1, 4, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(2), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 2 ^ 4);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList3[] = {1, 2, 3};
	int newList3[] = {2, 2, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(3), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 1 ^ 2);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList4[] = {1, 2, 3};
	int newList4[] = {1, 2, 2};
	enumerateChangesBetweenOldAndNewLists(listArgs(4), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 3 ^ 2);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 1);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList5[] = {1, 2, 3};
	int newList5[] = {1, 2, 4, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(5), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 4);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList6[] = {1, 2, 3};
	int newList6[] = {1, 2, 3, 4};
	enumerateChangesBetweenOldAndNewLists(listArgs(6), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 3);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 4);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList7[] = {1, 2, 3};
	int newList7[] = {4, 1, 2, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(7), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 4);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList8[] = {1, 2, 3};
	int newList8[] = {1, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(8), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList9[] = {1, 2, 3};
	int newList9[] = {1, 2};
	enumerateChangesBetweenOldAndNewLists(listArgs(9), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 3);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList10[] = {1, 2, 3};
	int newList10[] = {2, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(10), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList11[] = {1, 2, 3};
	int newList11[] = {1, 5, 2, 6, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(11), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 4);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[1].item, 5);
	TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[2].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[2].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[3].index, 3);
	TestCase_assertIntEqual(contextStruct.changes[3].item, 6);
	TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList12[] = {1, 5, 2, 6, 3};
	int newList12[] = {1, 2, 3};
	enumerateChangesBetweenOldAndNewLists(listArgs(12), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 4);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 5);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[1].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[2].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[2].item, 6);
	TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[3].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[3].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList13[] = {1, 2, 3};
	int newList13[] = {4, 5};
	enumerateChangesBetweenOldAndNewLists(listArgs(13), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 5);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[1].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[1].item, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_REMOVE);
	TestCase_assertIntEqual(contextStruct.changes[2].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[2].item, 3);
	TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[3].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[3].item, 4);
	TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
	TestCase_assertIntEqual(contextStruct.changes[4].changeType, CHANGE_TYPE_ADD);
	TestCase_assertIntEqual(contextStruct.changes[4].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[4].item, 5);
	TestCase_assertIntEqual(contextStruct.changes[4].compareResult, 3);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList14[] = {1, 2, 3};
	int newList14[] = {4, 2, 5};
	enumerateChangesBetweenOldAndNewLists(listArgs(14), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 1 ^ 4);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].item, 3 ^ 5);
	TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 2);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
	
	int oldList15[] = {1, 2, 3, 4};
	int newList15[] = {1, 5, 6, 4};
	enumerateChangesBetweenOldAndNewLists(listArgs(15), compare, itemAdded, itemRemoved, itemModified, &contextStruct);
	TestCase_assertUIntEqual(contextStruct.changeCount, 2);
	TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
	TestCase_assertIntEqual(contextStruct.changes[0].item, 2 ^ 5);
	TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_MODIFY);
	TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
	TestCase_assertIntEqual(contextStruct.changes[1].item, 3 ^ 6);
	TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 2);
	free(contextStruct.changes);
	contextStruct.changeCount = 0;
	contextStruct.changes = NULL;
}

static bool compareIdentifiable(void * oldItemUntyped, void * newItemUntyped, unsigned int * outCompareResult, void * context) {
	int oldItem = *(int *) oldItemUntyped, newItem = *(int *) newItemUntyped;
	int oldItemIdentifier = oldItem >> 4, newItemIdentifier = newItem >> 4;
	int oldItemValue = oldItem & 0xF, newItemValue = newItem & 0xF;
	if (oldItemIdentifier != newItemIdentifier) {
		return false;
	}
	if (oldItemValue == newItemValue) {
		*outCompareResult = 0;
	} else {
		*outCompareResult = (newItemValue > oldItemValue) + 1;
	}
	return true;
}

static void testEnumerateIdentifiableObjectChanges(void) {
	struct enumerateChangesContextStruct contextStruct = {0, NULL};
	
	{
		// No difference
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 0);
	}
	{
		// One value difference in the middle
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x24, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22 ^ 0x24);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One value difference at the start
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x10, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11 ^ 0x10);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 1);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One value difference at the end
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x22, 0x34};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x33 ^ 0x34);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One insertion in the middle
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x42, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x42);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One insertion at the start
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x00, 0x11, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x00);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One insertion at the end
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x22, 0x33, 0x44};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One deletion in the middle
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One deletion at the start
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One deletion at the end
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x22};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One object change (deletion and insertion) in the middle
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x42, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x42);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One object change (deletion and insertion) at the start
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x42, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x42);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// One object change (deletion and insertion) at the end
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x22, 0x42};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x42);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple value differences
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x13, 0x24, 0x30};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11 ^ 0x13);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x22 ^ 0x24);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 2);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x33 ^ 0x30);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 1);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple contiguous insertions in the middle
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x44, 0x55, 0x22, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple contiguous deletions in the middle
		int oldList[] = {0x11, 0x22, 0x33, 0x44, 0x55};
		int newList[] = {0x11, 0x22, 0x55};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Deletion in the middle followed by 2 insertions
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x44, 0x55, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple deletions in the middle followed by insertion
		int oldList[] = {0x11, 0x22, 0x33, 0x44};
		int newList[] = {0x11, 0x55, 0x44};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple noncontiguous insertions
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x44, 0x55, 0x22, 0x66, 0x77, 0x33};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 4);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 4);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x66);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[3].index, 5);
		TestCase_assertIntEqual(contextStruct.changes[3].item, 0x77);
		TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple noncontiguous deletions
		int oldList[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
		int newList[] = {0x11, 0x44, 0x77};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 4);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[3].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[3].item, 0x66);
		TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple contiguous modifications between two insertions
		int oldList[] = {0x11, 0x22, 0x33};
		int newList[] = {0x11, 0x44, 0x20, 0x30, 0x55};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 4);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x22 ^ 0x20);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 1);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x33 ^ 0x30);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 1);
		TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[3].index, 4);
		TestCase_assertIntEqual(contextStruct.changes[3].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple contiguous and noncontiguous deletions with a modification in between
		int oldList[] = {0x11, 0x22, 0x33, 0x44, 0x55};
		int newList[] = {0x30};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 5);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x33 ^ 0x30);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 1);
		TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[3].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[3].item, 0x44);
		TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[4].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[4].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[4].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[4].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// All items deleted
		int oldList[] = {0x11, 0x22};
		int newList[] = {};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Items added to empty list
		int oldList[] = {};
		int newList[] = {0x11, 0x22};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 2);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Multiple contiguous and noncontiguous insertions, deletions, and modifications
		int oldList[] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
		int newList[] = {0x0F, 0x30, 0x88, 0x99, 0x4F, 0xAA, 0xBB, 0xCC, 0x70};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 13);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x00 ^ 0x0F);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 7);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x77 ^ 0x70);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 1);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[3].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[3].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[3].item, 0x22);
		TestCase_assertIntEqual(contextStruct.changes[3].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[4].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[4].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[4].item, 0x33 ^ 0x30);
		TestCase_assertIntEqual(contextStruct.changes[4].compareResult, 1);
		TestCase_assertIntEqual(contextStruct.changes[5].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[5].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[5].item, 0x88);
		TestCase_assertIntEqual(contextStruct.changes[5].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[6].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[6].index, 3);
		TestCase_assertIntEqual(contextStruct.changes[6].item, 0x99);
		TestCase_assertIntEqual(contextStruct.changes[6].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[7].changeType, CHANGE_TYPE_MODIFY);
		TestCase_assertIntEqual(contextStruct.changes[7].index, 4);
		TestCase_assertIntEqual(contextStruct.changes[7].item, 0x44 ^ 0x4F);
		TestCase_assertIntEqual(contextStruct.changes[7].compareResult, 2);
		TestCase_assertIntEqual(contextStruct.changes[8].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[8].index, 5);
		TestCase_assertIntEqual(contextStruct.changes[8].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[8].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[9].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[9].index, 5);
		TestCase_assertIntEqual(contextStruct.changes[9].item, 0x66);
		TestCase_assertIntEqual(contextStruct.changes[9].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[10].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[10].index, 5);
		TestCase_assertIntEqual(contextStruct.changes[10].item, 0xAA);
		TestCase_assertIntEqual(contextStruct.changes[10].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[11].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[11].index, 6);
		TestCase_assertIntEqual(contextStruct.changes[11].item, 0xBB);
		TestCase_assertIntEqual(contextStruct.changes[11].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[12].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[12].index, 7);
		TestCase_assertIntEqual(contextStruct.changes[12].item, 0xCC);
		TestCase_assertIntEqual(contextStruct.changes[12].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Three noncontiguous additions
		int oldList[] = {0x22, 0x44};
		int newList[] = {0x11, 0x22, 0x33, 0x44, 0x55};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_ADD);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 4);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
	{
		// Three noncontiguous deletions
		int oldList[] = {0x11, 0x22, 0x33, 0x44, 0x55};
		int newList[] = {0x22, 0x44};
		enumerateIdentifiableObjectChanges(listArgs(), compareIdentifiable, itemAdded, itemRemoved, itemModified, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.changeCount, 3);
		TestCase_assertIntEqual(contextStruct.changes[0].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[0].index, 0);
		TestCase_assertIntEqual(contextStruct.changes[0].item, 0x11);
		TestCase_assertIntEqual(contextStruct.changes[0].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[1].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[1].index, 1);
		TestCase_assertIntEqual(contextStruct.changes[1].item, 0x33);
		TestCase_assertIntEqual(contextStruct.changes[1].compareResult, 3);
		TestCase_assertIntEqual(contextStruct.changes[2].changeType, CHANGE_TYPE_REMOVE);
		TestCase_assertIntEqual(contextStruct.changes[2].index, 2);
		TestCase_assertIntEqual(contextStruct.changes[2].item, 0x55);
		TestCase_assertIntEqual(contextStruct.changes[2].compareResult, 3);
		free(contextStruct.changes);
		contextStruct.changeCount = 0;
		contextStruct.changes = NULL;
	}
}

struct listSwapsContextStruct {
	unsigned int swapCount;
	struct {
		unsigned int oldIndex;
		unsigned int newIndex;
	} * swaps;
};

static void swapUInt(unsigned int oldIndex, unsigned int newIndex, void * context) {
	struct listSwapsContextStruct * contextStruct = context;
	contextStruct->swaps = realloc(contextStruct->swaps, (contextStruct->swapCount + 1) * sizeof(*contextStruct->swaps));
	contextStruct->swaps[contextStruct->swapCount].oldIndex = oldIndex;
	contextStruct->swaps[contextStruct->swapCount].newIndex = newIndex;
	contextStruct->swapCount++;
}

static bool compareUInt(void * lhsUntyped, void * rhsUntyped, void * context) {
	unsigned int * lhs = lhsUntyped, * rhs = rhsUntyped;
	return *lhs == *rhs;
}

static void testListReorderSwaps(void) {
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		listReorderSwaps(NULL, NULL, 0, sizeof(unsigned int), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 0);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1};
		unsigned int new[sizeof_count(old)] = {0, 1};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 0);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1};
		unsigned int new[sizeof_count(old)] = {1, 0};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 1);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2, 3};
		unsigned int new[sizeof_count(old)] = {0, 3, 1, 2};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[1].oldIndex, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[1].newIndex, 3);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2, 3, 4};
		unsigned int new[sizeof_count(old)] = {4, 3, 2, 1, 0};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[1].oldIndex, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[1].newIndex, 3);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2, 3, 4, 5};
		unsigned int new[sizeof_count(old)] = {1, 2, 3, 4, 5, 0};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 5);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 5);
		TestCase_assertUIntEqual(contextStruct.swaps[1].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[1].newIndex, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[2].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[2].newIndex, 3);
		TestCase_assertUIntEqual(contextStruct.swaps[3].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[3].newIndex, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[4].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[4].newIndex, 1);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2, 3, 4, 5};
		unsigned int new[sizeof_count(old)] = {5, 0, 1, 2, 3, 4};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 5);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[1].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[1].newIndex, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[2].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[2].newIndex, 3);
		TestCase_assertUIntEqual(contextStruct.swaps[3].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[3].newIndex, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[4].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[4].newIndex, 5);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2, 3, 4, 5, 6};
		unsigned int new[sizeof_count(old)] = {1, 2, 0, 3, 5, 6, 4};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[0].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[0].newIndex, 2);
		TestCase_assertUIntEqual(contextStruct.swaps[1].oldIndex, 0);
		TestCase_assertUIntEqual(contextStruct.swaps[1].newIndex, 1);
		TestCase_assertUIntEqual(contextStruct.swaps[2].oldIndex, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[2].newIndex, 6);
		TestCase_assertUIntEqual(contextStruct.swaps[3].oldIndex, 4);
		TestCase_assertUIntEqual(contextStruct.swaps[3].newIndex, 5);
		free(contextStruct.swaps);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 1, 2};
		unsigned int new[sizeof_count(old)] = {3, 4, 5};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 0);
	}
	{
		struct listSwapsContextStruct contextStruct = {0, NULL};
		unsigned int old[]                  = {0, 0, 0};
		unsigned int new[sizeof_count(old)] = {0, 0, 0};
		listReorderSwaps(old, new, sizeof_count(old), sizeof(old[0]), compareUInt, swapUInt, &contextStruct);
		TestCase_assertUIntEqual(contextStruct.swapCount, 0);
	}
}

static void testXorBytes(void) {
	uint8_t bytes1[8]   = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07};
	uint8_t bytes2[8]   = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
	uint8_t expected[8] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70};
	uint8_t result[8];
	memset(result, 0, sizeof(result));
	xorBytes(result, bytes1, bytes2, 8);
	TestCase_assertBytesEqual(result, expected, 8);
	
	memset(result, 0, sizeof(result));
	xorBytes(result, bytes1, bytes2, 7);
	TestCase_assertBytesEqual(result, expected, 7);
	TestCase_assertUIntEqual(result[7], 0);
	
	memset(result, 0, sizeof(result));
	xorBytes(result, bytes1, bytes2, 5);
	TestCase_assertBytesEqual(result, expected, 5);
	TestCase_assertUIntEqual(result[5], 0);
	
	memset(result, 0, sizeof(result));
	xorBytes(result, bytes1, bytes2, 4);
	TestCase_assertBytesEqual(result, expected, 4);
	TestCase_assertUIntEqual(result[4], 0);
}

TEST_SUITE(UndoUtilitiesTest,
           testEnumerateChangesBetweenOldAndNewLists,
           testEnumerateIdentifiableObjectChanges,
           testListReorderSwaps,
           testXorBytes)
