#include "unittest/TestSuite.h"
#include "inputcontroller/InputPlayback.h"
#include "utilities/IOUtilities.h"
#include "utilities/printfFormats.h"

static void verifyInit(InputPlayback * playback, InputController * controller, InputSession * session, unsigned int lineNumber) {
	TestCase_assert(playback != NULL, "Expected non-NULL but got NULL (%u)", lineNumber);
	TestCase_assert(playback->inputController == controller, "Expected %p but got %p (%u)", controller, playback->inputController, lineNumber);
	TestCase_assert(playback->inputSession == session, "Expected %p but got %p (%u)", session, playback->inputSession, lineNumber);
	TestCase_assert(playback->eventDispatcher != NULL, "Expected non-NULL but got NULL (%u)", lineNumber);
	TestCase_assert(playback->frameIndex == 0, "Expected 0 but got %u (%u)", playback->frameIndex, lineNumber);
	TestCase_assert(playback->vtable == &InputPlayback_class, "Expected %p but got %p (%u)", &InputPlayback_class, playback->vtable, lineNumber);
}

static void testInit() {
	InputPlayback playback, * playbackPtr;
	InputController * controller = (InputController *) 1;
	InputSession * session = InputSession_loadData("\x01\x00\x00\x00\x00\x00\x00\x00", 8, NULL);
	
	memset(&playback, 0x00, sizeof(playback));
	stemobject_assign_vtable(playback, InputPlayback);
	InputPlayback_init(&playback, NULL, NULL);
	verifyInit(&playback, NULL, NULL, __LINE__);
	InputPlayback_dispose(&playback);
	
	memset(&playback, 0xFF, sizeof(playback));
	stemobject_assign_vtable(playback, InputPlayback);
	InputPlayback_init(&playback, controller, session);
	verifyInit(&playback, controller, session, __LINE__);
	InputPlayback_dispose(&playback);
	
	playbackPtr = InputPlayback_create(controller, session);
	verifyInit(playbackPtr, controller, session, __LINE__);
	InputPlayback_dispose(playbackPtr);
	InputSession_dispose(session);
}

static unsigned int actionDownCalls, actionUpCalls, actionRepeatCalls, motionCalls;
static Atom lastDownActionID, lastUpActionID, lastRepeatActionID, lastMotionActionID;
static double lastDownTimestamp, lastUpTimestamp, lastRepeatTimestamp, lastMotionTimestamp;
static enum InputSessionEventType lastCallType;
static fixed16_16 lastMotionValueX, lastMotionValueY, lastMotionRawValueX, lastMotionRawValueY;

static bool actionDownCallback(Atom eventID, void * eventData, void * context) {
	struct InputController_event * event = eventData;
	actionDownCalls++;
	lastDownActionID = event->actionID;
	lastDownTimestamp = event->timestamp;
	lastCallType = INPUT_SESSION_EVENT_ACTION_DOWN;
	return true;
}

static bool actionUpCallback(Atom eventID, void * eventData, void * context) {
	struct InputController_event * event = eventData;
	actionUpCalls++;
	lastUpActionID = event->actionID;
	lastUpTimestamp = event->timestamp;
	lastCallType = INPUT_SESSION_EVENT_ACTION_UP;
	return true;
}

static bool actionRepeatCallback(Atom eventID, void * eventData, void * context) {
	struct InputController_event * event = eventData;
	actionRepeatCalls++;
	lastRepeatActionID = event->actionID;
	lastRepeatTimestamp = event->timestamp;
	lastCallType = INPUT_SESSION_EVENT_ACTION_REPEAT;
	return true;
}

static bool motionCallback(Atom eventID, void * eventData, void * context) {
	struct InputController_motionEvent * event = eventData;
	motionCalls++;
	lastMotionActionID = event->actionID;
	lastMotionTimestamp = event->timestamp;
	lastMotionValueX = event->valueX;
	lastMotionValueY = event->valueY;
	lastMotionRawValueX = event->rawValueX;
	lastMotionRawValueY = event->rawValueY;
	return true;
}

static void testBasicPlayback() {
	InputSession * session;
	InputPlayback * playback;
	InputController testInputController;
	char testData1[] = "\x01\x00" "\x00\x00\x00\x00" "\x01\x00" "\x61\x00" "\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x01";
	char testData2[] = "\x01\x00" "\x00\x00\x00\x00" "\x02\x00" "\x61\x00" "\x62\x00" "\x00\x00\x00\x00\x01\x00\x00" "\x01\x00\x00\x00\x00\x00\x00" "\x02\x00\x00\x00\x01\x00\x01" "\x01\x00\x00\x00\x00\x00\x02\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00";
	
	stemobject_assign_vtable(testInputController, InputController);
	InputController_init(&testInputController, NULL, NULL, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDownCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUpCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_REPEAT), actionRepeatCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_MOTION), motionCallback, NULL);
	
	session = InputSession_loadData(testData1, sizeof(testData1) - 1, NULL);
	playback = InputPlayback_create((InputController *) &testInputController, session);
	actionDownCalls = actionUpCalls = actionRepeatCalls = motionCalls = 0;
	
	InputPlayback_step(playback, 1.0);
	TestCase_assertUIntEqual(actionDownCalls, 1);
	TestCase_assertStringPointerEqual(lastDownActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastDownTimestamp, 1.0);
	TestCase_assertUIntEqual(actionUpCalls, 1);
	TestCase_assertStringPointerEqual(lastUpActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastUpTimestamp, 1.0);
	TestCase_assertIntEqual(lastCallType, INPUT_SESSION_EVENT_ACTION_UP);
	
	InputPlayback_dispose(playback);
	InputSession_dispose(session);
	
	session = InputSession_loadData(testData2, sizeof(testData2) - 1, NULL);
	playback = InputPlayback_create(&testInputController, session);
	actionDownCalls = actionUpCalls = actionRepeatCalls = motionCalls = 0;
	
	InputPlayback_step(playback, 2.0);
	TestCase_assertUIntEqual(actionDownCalls, 1);
	TestCase_assertStringPointerEqual(lastDownActionID, ATOM("b"));
	TestCase_assertDoubleEqual(lastDownTimestamp, 2.0);
	
	InputPlayback_step(playback, 3.0);
	TestCase_assertUIntEqual(actionDownCalls, 2);
	TestCase_assertStringPointerEqual(lastDownActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastDownTimestamp, 3.0);
	
	InputPlayback_step(playback, 4.0);
	TestCase_assertUIntEqual(actionDownCalls, 2);
	TestCase_assertUIntEqual(actionUpCalls, 0);
	
	InputPlayback_step(playback, 5.0);
	TestCase_assertUIntEqual(actionUpCalls, 1);
	TestCase_assertStringPointerEqual(lastUpActionID, ATOM("b"));
	TestCase_assertDoubleEqual(lastUpTimestamp, 5.0);
	
	InputPlayback_step(playback, 6.0);
	TestCase_assertUIntEqual(motionCalls, 1);
	TestCase_assertStringPointerEqual(lastMotionActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastMotionTimestamp, 6.0);
	TestCase_assertFixed16_16Equal(lastMotionValueX, 0x10000);
	TestCase_assertFixed16_16Equal(lastMotionValueY, 0x20000);
	TestCase_assertFixed16_16Equal(lastMotionRawValueX, 0x30000);
	TestCase_assertFixed16_16Equal(lastMotionRawValueY, 0x40000);
	
	InputPlayback_dispose(playback);
	InputSession_dispose(session);
}

static unsigned int playbackCompleteCalls;

bool playbackComplete(Atom eventID, void * eventData, void * context) {
	playbackCompleteCalls++;
	return true;
}

static void testPlaybackCompleteEvent() {
	InputSession * session;
	InputPlayback * playback;
	InputController testInputController;
	char testData1[] = "\x01\x00" "\x00\x00\x00\x00" "\x01\x00" "\x61\x00" "\x01\x00\x00\x00\x00\x00\x00";
	
	stemobject_assign_vtable(testInputController, InputController);
	InputController_init(&testInputController, NULL, NULL, NULL);
	playbackCompleteCalls = 0;
	
	session = InputSession_loadData(testData1, sizeof(testData1) - 1, NULL);
	playback = InputPlayback_create(&testInputController, session);
	EventDispatcher_registerForEvent(playback->eventDispatcher, ATOM(INPUT_PLAYBACK_EVENT_PLAYBACK_COMPLETE), playbackComplete, NULL);
	
	InputPlayback_step(playback, 1.0);
	TestCase_assertUIntEqual(playbackCompleteCalls, 0);
	InputPlayback_step(playback, 2.0);
	TestCase_assertUIntEqual(playbackCompleteCalls, 1);
	InputPlayback_step(playback, 3.0);
	TestCase_assertUIntEqual(playbackCompleteCalls, 1);
	
	InputPlayback_dispose(playback);
	InputSession_dispose(session);
}

static void testRewind() {
	InputSession * session;
	InputPlayback * playback;
	InputController testInputController;
	char testData1[] = "\x01\x00" "\x00\x00\x00\x00" "\x01\x00" "\x61\x00" "\x00\x00\x00\x00\x00\x00\x00" "\x00\x00\x00\x00\x00\x00\x01";
	
	stemobject_assign_vtable(testInputController, InputController);
	InputController_init(&testInputController, NULL, NULL, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_DOWN), actionDownCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_UP), actionUpCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_ACTION_REPEAT), actionRepeatCallback, NULL);
	EventDispatcher_registerForEvent(testInputController.eventDispatcher, ATOM(INPUT_CONTROLLER_EVENT_MOTION), motionCallback, NULL);
	
	session = InputSession_loadData(testData1, sizeof(testData1) - 1, NULL);
	playback = InputPlayback_create(&testInputController, session);
	actionDownCalls = actionUpCalls = 0;
	
	InputPlayback_step(playback, 1.0);
	TestCase_assertUIntEqual(actionDownCalls, 1);
	TestCase_assertStringPointerEqual(lastDownActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastDownTimestamp, 1.0);
	TestCase_assertUIntEqual(actionUpCalls, 1);
	TestCase_assertStringPointerEqual(lastUpActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastUpTimestamp, 1.0);
	TestCase_assertIntEqual(lastCallType, INPUT_SESSION_EVENT_ACTION_UP);
	
	InputPlayback_rewind(playback);
	InputPlayback_step(playback, 2.0);
	TestCase_assertUIntEqual(actionDownCalls, 2);
	TestCase_assertStringPointerEqual(lastDownActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastDownTimestamp, 2.0);
	TestCase_assertUIntEqual(actionUpCalls, 2);
	TestCase_assertStringPointerEqual(lastUpActionID, ATOM("a"));
	TestCase_assertDoubleEqual(lastUpTimestamp, 2.0);
	TestCase_assertIntEqual(lastCallType, INPUT_SESSION_EVENT_ACTION_UP);
	
	InputPlayback_dispose(playback);
	InputSession_dispose(session);
}

TEST_SUITE(InputPlaybackTest,
           testInit,
           testBasicPlayback,
           testPlaybackCompleteEvent,
           testRewind)
