#include "dynamictypes/DataArray.h"
#include "dynamictypes/DataHashTable.h"
#include "utilities/printfFormats.h"
#include "unittest/TestSuite.h"
#include <float.h>

static void testCreate() {
	DataHashTable * hashTable = hashCreate();
	TestCase_assertPointerNonNULL(hashTable);
	TestCase_assertPointerNonNULL(hashTable->hashTable);
	TestCase_assertSizeEqual(hashTable->hashTable->count, 0);
	hashDispose(hashTable);
}

static void testCreateWithKeysAndValues() {
	DataHashTable * hashTable = hashCreateWithKeysAndValues("a", valueCreateBoolean(false), NULL);
	TestCase_assertPointerNonNULL(hashTable);
	TestCase_assertSizeEqual(hashGetCount(hashTable), 1);
	DataValue * value = hashGet(hashTable, "a");
	TestCase_assertPointerNonNULL(value);
	TestCase_assertIntEqual(value->type, DATA_TYPE_BOOLEAN);
	TestCase_assertBoolFalse(value->value.boolean);
	hashDispose(hashTable);
	
	hashTable = hashCreateWithKeysAndValues("b", valueCreateBoolean(true), "foo", valueCreateInt8(2), NULL);
	TestCase_assertPointerNonNULL(hashTable);
	TestCase_assertSizeEqual(hashGetCount(hashTable), 2);
	value = hashGet(hashTable, "b");
	TestCase_assertPointerNonNULL(value);
	TestCase_assertIntEqual(value->type, DATA_TYPE_BOOLEAN);
	TestCase_assertBoolTrue(value->value.boolean);
	value = hashGet(hashTable, "foo");
	TestCase_assertPointerNonNULL(value);
	TestCase_assertIntEqual(value->type, DATA_TYPE_INT8);
	TestCase_assertIntEqual(value->value.int8, 2);
	hashDispose(hashTable);
}

static void testAccessors() {
	DataHashTable * hashTable = hashCreate();
	TestCase_assertPointerNonNULL(hashTable);
	
	// Verify table empty
	TestCase_assertBoolFalse(hashHas(hashTable, "value1"));
	TestCase_assertBoolFalse(hashHas(hashTable, "value2"));
	
	// Verify hashGet returns NULL for unset keys
	DataValue * entry = hashGet(hashTable, "value1");
	TestCase_assertPointerNULL(entry);
	entry = hashGet(hashTable, "value2");
	TestCase_assertPointerNULL(entry);
	
	// Set values
	hashSet(hashTable, "value1", valueCreateBoolean(false));
	TestCase_assertSizeEqual(hashGetCount(hashTable), 1);
	hashSet(hashTable, "value2", valueCreateBoolean(true));
	TestCase_assertSizeEqual(hashGetCount(hashTable), 2);
	
	// Verify table full
	TestCase_assertBoolTrue(hashHas(hashTable, "value1"));
	TestCase_assertBoolTrue(hashHas(hashTable, "value2"));
	
	// Verify hashGet returns correct values for set keys
	entry = hashGet(hashTable, "value1");
	TestCase_assertPointerNonNULL(entry);
	TestCase_assertIntEqual(entry->type, DATA_TYPE_BOOLEAN);
	TestCase_assertBoolFalse(entry->value.boolean);
	entry = hashGet(hashTable, "value2");
	TestCase_assertPointerNonNULL(entry);
	TestCase_assertIntEqual(entry->type, DATA_TYPE_BOOLEAN);
	TestCase_assertBoolTrue(entry->value.boolean);
}

static void testCopy() {
	DataHashTable * hashTable = hashCreate();
	DataHashTable * hashTableCopy = hashCopy(hashTable);
	TestCase_assertPointerNonNULL(hashTableCopy);
	TestCase_assertPointerUnequal(hashTableCopy, hashTable);
	TestCase_assertSizeEqual(hashGetCount(hashTable), hashGetCount(hashTableCopy));
	hashDispose(hashTable);
	hashDispose(hashTableCopy);
	
	hashTable = hashCreate();
	hashSet(hashTable, "a", valueCreateString("hello", DATA_USE_STRLEN, true, true));
	hashSet(hashTable, "b", valueCreateBlob("foo", 3, true, true));
	DataHashTable * subtable = hashCreate();
	hashSet(subtable, "c", valueCreateString("foo", DATA_USE_STRLEN, true, true));
	hashSet(subtable, "d", valueCreatePointer((void *) 0x1234));
	hashSet(hashTable, "c", valueCreateHashTable(subtable, true, true));
	hashDispose(subtable);
	
	hashTableCopy = hashCopy(hashTable);
	TestCase_assertPointerNonNULL(hashTableCopy);
	TestCase_assertPointerUnequal(hashTableCopy, hashTable);
	
	TestCase_assertSizeEqual(hashGetCount(hashTable), hashGetCount(hashTableCopy));
	
	DataValue * string = hashGet(hashTable, "a");
	DataValue * stringCopy = hashGet(hashTableCopy, "a");
	TestCase_assertPointerNonNULL(string);
	TestCase_assertPointerNonNULL(stringCopy);
	TestCase_assertIntEqual(string->type, DATA_TYPE_STRING);
	TestCase_assertIntEqual(stringCopy->type, DATA_TYPE_STRING);
	TestCase_assertStringEqual(string->value.string, "hello");
	TestCase_assertStringEqual(stringCopy->value.string, "hello");
	TestCase_assertPointerUnequal(string->value.string, stringCopy->value.string);
	
	DataValue * blob = hashGet(hashTable, "b");
	DataValue * blobCopy = hashGet(hashTableCopy, "b");
	TestCase_assertPointerNonNULL(blob);
	TestCase_assertPointerNonNULL(blobCopy);
	TestCase_assertSizeEqual(blob->value.blob.length, 3);
	TestCase_assertSizeEqual(blobCopy->value.blob.length, 3);
	TestCase_assert(!memcmp(blob->value.blob.bytes, "foo", 3), "Expected \"foo\" but got \"%.*s\"", (unsigned int) blob->value.blob.length, (char *) blob->value.blob.bytes);
	TestCase_assert(!memcmp(blobCopy->value.blob.bytes, "foo", 3), "Expected \"foo\" but got \"%.*s\"", (unsigned int) blobCopy->value.blob.length, (char *) blobCopy->value.blob.bytes);
	TestCase_assertPointerUnequal(blob->value.blob.bytes, blobCopy->value.blob.bytes);
	
	DataValue * subtableValue = hashGet(hashTable, "c");
	DataValue * subtableValueCopy = hashGet(hashTableCopy, "c");
	TestCase_assertPointerNonNULL(subtableValue);
	TestCase_assertPointerNonNULL(subtableValueCopy);
	TestCase_assertSizeEqual(hashGetCount(subtableValue->value.hashTable), 2);
	TestCase_assertSizeEqual(hashGetCount(subtableValueCopy->value.hashTable), 2);
	TestCase_assertPointerUnequal(subtableValue->value.hashTable, subtableValueCopy->value.hashTable);
	
	hashDispose(hashTable);
	hashDispose(hashTableCopy);
}

static void testReplaceValues() {
	DataHashTable * hash = hashCreate();
	hashSet(hash, "a", valueCreateInt32(1));
	hashSet(hash, "b", valueCreateInt32(2));
	TestCase_assertSizeEqual(hashGetCount(hash), 2);
	DataValue * int32Value = hashGet(hash, "b");
	TestCase_assertIntEqual(int32Value->value.int32, 2);
	
	hashSet(hash, "b", valueCreateInt32(3));
	TestCase_assertSizeEqual(hashGetCount(hash), 2);
	int32Value = hashGet(hash, "b");
	TestCase_assertIntEqual(int32Value->value.int32, 3);
	
	hashSet(hash, "a", valueCreateString("hello", DATA_USE_STRLEN, false, false));
	TestCase_assertSizeEqual(hashGetCount(hash), 2);
	DataValue * stringValue = hashGet(hash, "a");
	TestCase_assertPointerNonNULL(stringValue);
	TestCase_assertIntEqual(stringValue->type, DATA_TYPE_STRING);
	TestCase_assertStringEqual(stringValue->value.string, "hello");
	
	hashSet(hash, "a", valueCreateInt32(1));
	TestCase_assertSizeEqual(hashGetCount(hash), 2);
	int32Value = hashGet(hash, "a");
	TestCase_assertIntEqual(int32Value->value.int32, 1);
	
	size_t count;
	const char ** keys = hashGetKeys(hash, &count);
	TestCase_assertSizeEqual(count, 2);
	TestCase_assert((!strcmp(keys[0], "a") && !strcmp(keys[1], "b")) || (!strcmp(keys[0], "b") && !strcmp(keys[1], "a")), "Expected \"a\" and \"b\", but got \"%s\" and \"%s\"", keys[0], keys[1]);
	free(keys);
}

static void testDeleteValues() {
	DataHashTable * hash = hashCreate();
	hashSet(hash, "a", valueCreateInt32(1));
	hashSet(hash, "b", valueCreateInt32(2));
	hashSet(hash, "c", valueCreateInt32(3));
	TestCase_assertSizeEqual(hashGetCount(hash), 3);
	
	bool found = hashDelete(hash, "d");
	TestCase_assertBoolFalse(found);
	
	found = hashDelete(hash, "b");
	TestCase_assertBoolTrue(found);
	TestCase_assertSizeEqual(hashGetCount(hash), 2);
	TestCase_assertBoolFalse(hashHas(hash, "b"));
	size_t count;
	const char ** keys = hashGetKeys(hash, &count);
	TestCase_assertSizeEqual(count, 2);
	TestCase_assert((!strcmp(keys[0], "a") && !strcmp(keys[1], "c")) || (!strcmp(keys[0], "c") && !strcmp(keys[1], "a")), "Expected \"a\" and \"c\", but got \"%s\" and \"%s\"", keys[0], keys[1]);
	free(keys);
	
	found = hashDelete(hash, "b");
	TestCase_assertBoolFalse(found);
	
	found = hashDelete(hash, "a");
	TestCase_assertBoolTrue(found);
	TestCase_assertSizeEqual(hashGetCount(hash), 1);
	TestCase_assertBoolFalse(hashHas(hash, "a"));
	keys = hashGetKeys(hash, &count);
	TestCase_assertSizeEqual(count, 1);
	TestCase_assertStringEqual(keys[0], "c");
	free(keys);
	
	hashSet(hash, "d", valueCreateUInt16(3));
	hashDeleteAll(hash);
	TestCase_assertSizeEqual(hashGetCount(hash), 0);
	
	hashDispose(hash);
}

static void testGetKeys() {
	DataHashTable * hashTable = hashCreate();
	hashSet(hashTable, "a", valueCreateBoolean(false));
	size_t count;
	const char ** keys = hashGetKeys(hashTable, &count);
	
	TestCase_assertPointerNonNULL(keys);
	TestCase_assertSizeEqual(count, 1);
	TestCase_assertStringEqual(keys[0], "a");
	
	hashDispose(hashTable);
	free(keys);
	
	hashTable = hashCreate();
	hashSet(hashTable, "bar", valueCreateBoolean(true));
	hashSet(hashTable, "foo", valueCreateInt32(1));
	count = 0;
	keys = hashGetKeys(hashTable, &count);
	
	TestCase_assertPointerNonNULL(keys);
	TestCase_assertSizeEqual(count, 2);
	TestCase_assert((!strcmp(keys[0], "foo") && !strcmp(keys[1], "bar")) || (!strcmp(keys[0], "bar") && !strcmp(keys[1], "foo")), "Expected \"foo\" and \"bar\" but got \"%s\" and \"%s\"", keys[0], keys[1]);
	
	hashDispose(hashTable);
	free(keys);
}

static void testGetKeyAtIndex() {
	DataHashTable * hashTable = hashCreate();
	const char * key = hashGetKeyAtIndex(hashTable, 0);
	TestCase_assertPointerNULL(key);
	
	hashSet(hashTable, "a", valueCreateBoolean(false));
	key = hashGetKeyAtIndex(hashTable, 0);
	TestCase_assertPointerNonNULL(key);
	TestCase_assertStringEqual(key, "a");
	key = hashGetKeyAtIndex(hashTable, 1);
	TestCase_assertPointerNULL(key);
	
	hashDispose(hashTable);
	
	hashTable = hashCreate();
	hashSet(hashTable, "bar", valueCreateBoolean(true));
	hashSet(hashTable, "foo", valueCreateInt32(1));
	
	key = hashGetKeyAtIndex(hashTable, 0);
	TestCase_assertPointerNonNULL(key);
	if (!strcmp(key, "foo")) {
		key = hashGetKeyAtIndex(hashTable, 1);
		TestCase_assertPointerNonNULL(key);
		TestCase_assertStringEqual(key, "bar");
		
	} else if (!strcmp(key, "bar")) {
		key = hashGetKeyAtIndex(hashTable, 1);
		TestCase_assertPointerNonNULL(key);
		TestCase_assertStringEqual(key, "foo");
		
	} else {
		TestCase_assert(false, "First key was neither expected value \"foo\" nor \"bar\"; got \"%s\" instead", key);
	}
	
	hashDispose(hashTable);
}

TEST_SUITE(DataHashTableTest,
           testCreate,
           testCreateWithKeysAndValues,
           testAccessors,
           testCopy,
           testReplaceValues,
           testDeleteValues,
           testGetKeys,
           testGetKeyAtIndex)
