#include "inputcontroller/InputRecorder.h"
#include "unittest/TestSuite.h"
#include "utilities/IOUtilities.h"
#include "utilities/printfFormats.h"
#include <stdbool.h>
#include <unistd.h>

static void verifyInit(InputRecorder * inputRecorder, InputController * inputController, bool fileOutput, unsigned int lineNumber) {
	TestCase_assert(inputRecorder != NULL, "Expected non-NULL but got NULL (%u)", lineNumber);
	TestCase_assert(inputRecorder->vtable == &InputRecorder_class, "Expected %p but got %p (%u)", &InputRecorder_class, inputRecorder->vtable, lineNumber);
	TestCase_assert(inputRecorder->inputController == inputController, "Expected %p but got %p (%u)", inputController, inputRecorder->inputController, lineNumber);
	TestCase_assert(inputRecorder->frameIndex == 0, "Expected 0 but got %u (%u)", inputRecorder->frameIndex, lineNumber);
	if (fileOutput) {
		TestCase_assert(inputRecorder->outputFile != NULL, "Expected non-NULL but got NULL (%u)", lineNumber);
	} else {
		TestCase_assert(inputRecorder->outputFile == NULL, "Expected NULL but got %p (%u)", inputRecorder->outputFile, lineNumber);
		TestCase_assert(inputRecorder->memwriteContext.realloc, "Expected true but got false (%u)", lineNumber);
	}
}

static void testInit() {
	InputRecorder inputRecorder, * inputRecorderPtr;
	InputController * inputController;
	const char * tempFilePath;
	int tempFD;
	
	inputController = InputController_create(NULL, NULL, NULL);
	
	memset(&inputRecorder, 0x00, sizeof(InputRecorder));
	stemobject_assign_vtable(inputRecorder, InputRecorder);
	InputRecorder_initWithMemwriteOutput(&inputRecorder, NULL, NULL, 0);
	verifyInit(&inputRecorder, NULL, NULL, __LINE__);
	InputRecorder_dispose(&inputRecorder);
	
	memset(&inputRecorder, 0xFF, sizeof(InputRecorder));
	stemobject_assign_vtable(inputRecorder, InputRecorder);
	InputRecorder_initWithMemwriteOutput(&inputRecorder, inputController, NULL, 0);
	verifyInit(&inputRecorder, inputController, NULL, __LINE__);
	InputRecorder_dispose(&inputRecorder);
	
	inputRecorderPtr = InputRecorder_createWithMemwriteOutput(NULL, NULL, 0);
	verifyInit(inputRecorderPtr, NULL, NULL, __LINE__);
	InputRecorder_dispose(inputRecorderPtr);
	
	tempFilePath = temporaryFilePath("tmpXXXXXX", &tempFD);
	close(tempFD);
	
	memset(&inputRecorder, 0x00, sizeof(InputRecorder));
	stemobject_assign_vtable(inputRecorder, InputRecorder);
	InputRecorder_initWithFileOutput(&inputRecorder, NULL, NULL, 0, tempFilePath);
	verifyInit(&inputRecorder, NULL, tempFilePath, __LINE__);
	InputRecorder_dispose(&inputRecorder);
	
	memset(&inputRecorder, 0xFF, sizeof(InputRecorder));
	stemobject_assign_vtable(inputRecorder, InputRecorder);
	InputRecorder_initWithFileOutput(&inputRecorder, inputController, NULL, 0, tempFilePath);
	verifyInit(&inputRecorder, inputController, tempFilePath, __LINE__);
	InputRecorder_dispose(&inputRecorder);
	
	inputRecorderPtr = InputRecorder_createWithFileOutput(NULL, NULL, 0, tempFilePath);
	verifyInit(inputRecorderPtr, NULL, tempFilePath, __LINE__);
	InputRecorder_dispose(inputRecorderPtr);
	
	InputController_dispose(inputController);
	unlink(tempFilePath);
}

static void testMemwriteRecording() {
	InputRecorder * inputRecorder;
	InputController * inputController;
	
	inputController = InputController_create(NULL, NULL, NULL);
	
	inputRecorder = InputRecorder_createWithMemwriteOutput(inputController, NULL, 0);
	TestCase_assert(inputRecorder->memwriteContext.length == 8, "Expected 8 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	unsigned char * data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data, "\x01\x00\x00\x00\x00\x00\x00\x00", 8), "Expected 0100 00000000 0000 but got %02X%02X %02X%02X%02X%02X %02X%02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
	InputRecorder_dispose(inputRecorder);
	
	inputRecorder = InputRecorder_createWithMemwriteOutput(inputController, "abcd", 4);
	TestCase_assert(inputRecorder->memwriteContext.length == 12, "Expected 12 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data, "\x01\x00\x04\x00\x00\x00""abcd\x00\x00", 12), "Expected 0100 04000000 61626364 0000 but got %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
	InputRecorder_dispose(inputRecorder);
	
	InputController_dispose(inputController);
	inputController = InputController_create(NULL, NULL, "a", "bc", NULL);
	
	inputRecorder = InputRecorder_createWithMemwriteOutput(inputController, NULL, 0);
	TestCase_assert(inputRecorder->memwriteContext.length == 13, "Expected 13 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data, "\x01\x00\x00\x00\x00\x00\x02\x00""a\x00""bc\x00", 13), "Expected 0100 00000000 0200 6100 626300 but got %02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X%02X", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12]);
	
	call_virtual(startAction, inputController, ATOM("a"), 1.0, false, InputMap_genericUnknownBinding());
	TestCase_assert(inputRecorder->memwriteContext.length == 20, "Expected 20 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data + 13, "\x00\x00\x00\x00\x00\x00\x00", 7), "Expected 00000000 0000 00 but got %02X%02X%02X%02X %02X%02X %02X", data[13], data[14], data[15], data[16], data[17], data[18], data[19]);
	
	InputRecorder_nextFrame(inputRecorder);
	call_virtual(startAction, inputController, ATOM("bc"), 2.0, false, InputMap_genericUnknownBinding());
	TestCase_assert(inputRecorder->memwriteContext.length == 27, "Expected 27 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data + 20, "\x01\x00\x00\x00\x01\x00\x00", 7), "Expected 01000000 0100 00 but got %02X%02X%02X%02X %02X%02X %02X", data[20], data[21], data[22], data[23], data[24], data[25], data[26]);
	
	InputRecorder_nextFrame(inputRecorder);
	InputRecorder_nextFrame(inputRecorder);
	InputRecorder_nextFrame(inputRecorder);
	call_virtual(stopAction, inputController, ATOM("bc"), 3.0, false);
	TestCase_assert(inputRecorder->memwriteContext.length == 34, "Expected 34 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data + 27, "\x03\x00\x00\x00\x01\x00\x01", 7), "Expected 03000000 0100 01 but got %02X%02X%02X%02X %02X%02X %02X", data[27], data[28], data[29], data[30], data[31], data[32], data[33]);
	
	call_virtual(motion, inputController, ATOM("a"), 4.0, 0x10000, 0x20000, 0x30000, 0x40000, false);
	TestCase_assert(inputRecorder->memwriteContext.length == 57, "Expected 57 but got " SIZE_T_FORMAT, inputRecorder->memwriteContext.length);
	data = inputRecorder->memwriteContext.data;
	TestCase_assert(!memcmp(inputRecorder->memwriteContext.data + 34, "\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00", 23), "Expected 00000000 0000 02 00000100 00000200 00000300 00000400 but got %02X%02X%02X%02X %02X%02X %02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X", data[34], data[35], data[36], data[37], data[38], data[39], data[40], data[41], data[42], data[43], data[44], data[45], data[46], data[47], data[48], data[49], data[50], data[51], data[52], data[53], data[54], data[55], data[56]);
	
	InputRecorder_dispose(inputRecorder);
	InputController_dispose(inputController);
}

static void testFileRecording() {
	InputRecorder * inputRecorder;
	InputController * inputController;
	const char * tempFilePath;
	int tempFD;
	unsigned char * fileContents;
	size_t fileLength;
	
	inputController = InputController_create(NULL, NULL, NULL);
	tempFilePath = temporaryFilePath("tmpXXXXXX", &tempFD);
	close(tempFD);
	
	inputRecorder = InputRecorder_createWithFileOutput(inputController, NULL, 0, tempFilePath);
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 8, "Expected 8 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents, "\x01\x00\x00\x00\x00\x00\x00\x00", 8), "Expected 0100 00000000 0000 but got %02X%02X %02X%02X%02X%02X %02X%02X", fileContents[0], fileContents[1], fileContents[2], fileContents[3], fileContents[4], fileContents[5], fileContents[6], fileContents[7]);
	free(fileContents);
	InputRecorder_dispose(inputRecorder);
	
	inputRecorder = InputRecorder_createWithFileOutput(inputController, "abcd", 4, tempFilePath);
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 12, "Expected 12 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents, "\x01\x00\x04\x00\x00\x00""abcd\x00\x00", 12), "Expected 0100 04000000 61626364 0000 but got %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X", fileContents[0], fileContents[1], fileContents[2], fileContents[3], fileContents[4], fileContents[5], fileContents[6], fileContents[7], fileContents[8], fileContents[9], fileContents[10], fileContents[11]);
	free(fileContents);
	InputRecorder_dispose(inputRecorder);
	
	InputController_dispose(inputController);
	inputController = InputController_create(NULL, NULL, "a", "bc", NULL);
	
	inputRecorder = InputRecorder_createWithFileOutput(inputController, NULL, 0, tempFilePath);
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 13, "Expected 13 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents, "\x01\x00\x00\x00\x00\x00\x02\x00""a\x00""bc\x00", 13), "Expected 0100 00000000 0200 6100 626300 but got %02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X%02X", fileContents[0], fileContents[1], fileContents[2], fileContents[3], fileContents[4], fileContents[5], fileContents[6], fileContents[7], fileContents[8], fileContents[9], fileContents[10], fileContents[11], fileContents[12]);
	free(fileContents);
	
	call_virtual(startAction, inputController, ATOM("a"), 1.0, false, InputMap_genericUnknownBinding());
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 20, "Expected 20 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents + 13, "\x00\x00\x00\x00\x00\x00\x00", 7), "Expected 00000000 0000 00 but got %02X%02X%02X%02X %02X%02X %02X", fileContents[13], fileContents[14], fileContents[15], fileContents[16], fileContents[17], fileContents[18], fileContents[19]);
	free(fileContents);
	
	InputRecorder_nextFrame(inputRecorder);
	call_virtual(startAction, inputController, ATOM("bc"), 2.0, false, InputMap_genericUnknownBinding());
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 27, "Expected 27 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents + 20, "\x01\x00\x00\x00\x01\x00\x00", 7), "Expected 01000000 0100 00 but got %02X%02X%02X%02X %02X%02X %02X", fileContents[20], fileContents[21], fileContents[22], fileContents[23], fileContents[24], fileContents[25], fileContents[26]);
	free(fileContents);
	
	InputRecorder_nextFrame(inputRecorder);
	InputRecorder_nextFrame(inputRecorder);
	InputRecorder_nextFrame(inputRecorder);
	call_virtual(stopAction, inputController, ATOM("bc"), 3.0, false);
	fileContents = readFileSimple(tempFilePath, &fileLength);
	TestCase_assert(fileContents != NULL, "Expected non-NULL but got NULL");
	TestCase_assert(fileLength == 34, "Expected 31 but got " SIZE_T_FORMAT, fileLength);
	TestCase_assert(!memcmp(fileContents + 27, "\x03\x00\x00\x00\x01\x00\x01", 7), "Expected 03000000 0100 01 but got %02X%02X%02X%02X %02X%02X %02X", fileContents[27], fileContents[28], fileContents[29], fileContents[30], fileContents[31], fileContents[32], fileContents[33]);
	free(fileContents);
	
	InputRecorder_dispose(inputRecorder);
	InputController_dispose(inputController);
	unlink(tempFilePath);
}

TEST_SUITE(InputRecorderTest,
           testInit,
           testMemwriteRecording,
           testFileRecording)
