#include "utilities/UndoTree.h"
#include "unittest/TestSuite.h"
#include "unittest/TestUndoStateDelta.h"

static void verifyInit(UndoTree * object, void * target, unsigned int line) {
	TestCase_assert(object->vtable == &UndoTree_class, "Expected %p but got %p (line %u)", &UndoTree_class, object->vtable, line);
	TestCase_assert(object->vtable->dispose == UndoTree_dispose, "Expected %p but got %p (line %u)", UndoTree_dispose, object->vtable->dispose, line);
	TestCase_assert(object->nodeCount == 0, "Expected 0 but got %u (line %u)", object->nodeCount, line);
	TestCase_assert(object->currentNodeID == 0, "Expected 0 but got %u (line %u)", object->currentNodeID, line);
	TestCase_assert(object->target == target, "Expected %p but got %p (line %u)", target, object->target, line);
}

static void testInit(void) {
	UndoTree object, * objectPtr;
	bool success;
	
	memset(&object, 0x00, sizeof(object));
	stemobject_assign_vtable(object, UndoTree);
	success = UndoTree_init(&object, (void *) 0x1);
	TestCase_assertBoolTrue(success);
	verifyInit(&object, (void *) 0x1, __LINE__);
	UndoTree_dispose(&object);
	
	memset(&object, 0xFF, sizeof(object));
	stemobject_assign_vtable(object, UndoTree);
	success = UndoTree_init(&object, (void *) 0x2);
	TestCase_assertBoolTrue(success);
	verifyInit(&object, (void *) 0x2, __LINE__);
	UndoTree_dispose(&object);
	
	objectPtr = UndoTree_create((void *) 0x3);
	TestCase_assertPointerNonNULL(objectPtr);
	if (objectPtr == NULL) { return; } // Suppress clang warning
	verifyInit(objectPtr, (void *) 0x3, __LINE__);
	UndoTree_dispose(objectPtr);
}

static void testBasicOperations(void) {
	UndoTree * undoTree = UndoTree_create((void *) 0x4);
	TestCase_assertBoolFalse(UndoTree_canUndo(undoTree));
	TestCase_assertBoolFalse(UndoTree_canRedo(undoTree));
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 0);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForUndo(undoTree));
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedo(undoTree));
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 0));
	TestCase_assertBoolFalse(UndoTree_undo(undoTree));
	TestCase_assertBoolFalse(UndoTree_redo(undoTree));
	TestCase_assertBoolFalse(UndoTree_redoWithTimeline(undoTree, 0));
	TestCase_assertBoolFalse(UndoTree_traverseToNode(undoTree, 0));
	
	TestUndoStateDelta * stateDelta = TestUndoStateDelta_create();
	TestCase_assertUIntEqual(undoTree->currentNodeID, 0);
	UndoTree_appendNode(undoTree, stateDelta, false);
	TestCase_assertUIntEqual(undoTree->currentNodeID, 1);
	TestCase_assertBoolTrue(UndoTree_canUndo(undoTree));
	TestCase_assertBoolFalse(UndoTree_canRedo(undoTree));
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 0);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForUndo(undoTree), stateDelta);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedo(undoTree));
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 0));
	TestCase_assertBoolFalse(UndoTree_redo(undoTree));
	TestCase_assertBoolFalse(UndoTree_redoWithTimeline(undoTree, 0));
	TestCase_assertBoolFalse(UndoTree_traverseToNode(undoTree, 1));
	
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 0);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 1);
	TestCase_assertPointerEqual(stateDelta->lastTarget, (void *) 0x4);
	
	TestCase_assertBoolFalse(UndoTree_canUndo(undoTree));
	TestCase_assertBoolTrue(UndoTree_canRedo(undoTree));
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 1);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForUndo(undoTree));
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForRedo(undoTree), stateDelta);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 0), stateDelta);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 1));
	TestCase_assertBoolFalse(UndoTree_undo(undoTree));
	TestCase_assertBoolFalse(UndoTree_redoWithTimeline(undoTree, 1));
	TestCase_assertBoolFalse(UndoTree_traverseToNode(undoTree, 0));
	
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 0);
	undoTree->target = (void *) 0x5;
	TestCase_assertBoolTrue(UndoTree_redo(undoTree));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 1);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 1);
	TestCase_assertPointerEqual(stateDelta->lastTarget, (void *) 0x5);
	
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 2);
	TestCase_assertPointerEqual(stateDelta->lastTarget, (void *) 0x5);
	
	undoTree->target = (void *) 0x6;
	TestCase_assertBoolFalse(UndoTree_redoWithTimeline(undoTree, 1));
	TestCase_assertBoolTrue(UndoTree_redoWithTimeline(undoTree, 0));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 1);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 2);
	TestCase_assertPointerEqual(stateDelta->lastTarget, (void *) 0x6);
	
	TestUndoStateDelta * stateDelta2 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta2, false);
	TestCase_assertUIntEqual(undoTree->currentNodeID, 2);
	TestCase_assertBoolTrue(UndoTree_canUndo(undoTree));
	TestCase_assertBoolFalse(UndoTree_canRedo(undoTree));
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 0);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForUndo(undoTree), stateDelta2);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedo(undoTree));
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 0));
	TestCase_assertBoolFalse(UndoTree_redo(undoTree));
	TestCase_assertBoolFalse(UndoTree_redoWithTimeline(undoTree, 0));
	
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 1);
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 1);
	
	TestCase_assertBoolTrue(UndoTree_canUndo(undoTree));
	TestCase_assertBoolTrue(UndoTree_canRedo(undoTree));
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 1);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForUndo(undoTree), stateDelta);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForRedo(undoTree), stateDelta2);
	TestCase_assertPointerEqual(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 0), stateDelta2);
	TestCase_assertPointerNULL(UndoTree_getStateDeltaForRedoWithTimeline(undoTree, 1));
	
	TestCase_assertBoolTrue(UndoTree_traverseToNode(undoTree, 0));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 0);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 3);
	
	TestCase_assertBoolTrue(UndoTree_traverseToNode(undoTree, 2));
	TestCase_assertUIntEqual(undoTree->currentNodeID, 2);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 3);
	TestCase_assertUIntEqual(stateDelta2->applyCallCount, 1);
	
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 0);
	UndoTree_dispose(undoTree);
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 1);
	free(stateDelta);
}

static void testTimelines(void) {
	UndoTree * undoTree = UndoTree_create(NULL);
	
	TestUndoStateDelta * stateDelta = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta, true);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 1);
	
	TestUndoStateDelta * stateDelta2 = TestUndoStateDelta_create();
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 0);
	UndoTree_appendNode(undoTree, stateDelta2, true);
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 1);
	free(stateDelta);
	TestCase_assertUIntEqual(undoTree->nodeCount, 1);
	
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 1);
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 1);
	stateDelta = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta, false);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 1);
	
	TestCase_assertUIntEqual(UndoTree_getRedoTimelineCountFromCurrentNode(undoTree), 2);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_redo(undoTree));
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 1);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 1);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 2);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 1);
	TestCase_assertBoolTrue(UndoTree_redoWithTimeline(undoTree, 1));
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 2);
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 2);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta->revertCallCount, 3);
	TestCase_assertUIntEqual(stateDelta2->applyCallCount, 0);
	TestCase_assertBoolTrue(UndoTree_redoWithTimeline(undoTree, 0));
	TestCase_assertUIntEqual(stateDelta2->applyCallCount, 1);
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 1);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 2);
	TestCase_assertUIntEqual(stateDelta2->applyCallCount, 1);
	TestCase_assertBoolTrue(UndoTree_redo(undoTree));
	TestCase_assertUIntEqual(stateDelta2->applyCallCount, 2);
	
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 2);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 2);
	TestCase_assertBoolTrue(UndoTree_traverseToNode(undoTree, 3));
	TestCase_assertUIntEqual(stateDelta2->revertCallCount, 3);
	TestCase_assertUIntEqual(stateDelta->applyCallCount, 3);
	
	TestUndoStateDelta * stateDelta3 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta3, false);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestUndoStateDelta * stateDelta4 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta4, false);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestUndoStateDelta * stateDelta5 = TestUndoStateDelta_create();
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta3->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta4->disposeCallCount, 0);
	UndoTree_appendNode(undoTree, stateDelta5, true);
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta3->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta4->disposeCallCount, 1);
	free(stateDelta);
	free(stateDelta2);
	free(stateDelta3);
	free(stateDelta4);
	
	stateDelta = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta, false);
	stateDelta2 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta2, false);
	stateDelta3 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta3, false);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	stateDelta4 = TestUndoStateDelta_create();
	UndoTree_appendNode(undoTree, stateDelta4, false);
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	TestCase_assertBoolTrue(UndoTree_undo(undoTree));
	stateDelta5 = TestUndoStateDelta_create();
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta3->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta4->disposeCallCount, 0);
	UndoTree_appendNode(undoTree, stateDelta5, true);
	TestCase_assertUIntEqual(stateDelta2->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta3->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta4->disposeCallCount, 1);
	free(stateDelta2);
	free(stateDelta3);
	free(stateDelta4);
	
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 0);
	TestCase_assertUIntEqual(stateDelta5->disposeCallCount, 0);
	UndoTree_dispose(undoTree);
	TestCase_assertUIntEqual(stateDelta->disposeCallCount, 1);
	TestCase_assertUIntEqual(stateDelta5->disposeCallCount, 1);
	free(stateDelta);
	free(stateDelta5);
}

TEST_SUITE(UndoTreeTest,
           testInit,
           testBasicOperations,
           testTimelines)
