#include "../kvstore.c"
#include "test_help.h"

uint64_t hashTestCallback(const void *key) {
    return hashtableGenHashFunction((char *)key, strlen((char *)key));
}

int cmpTestCallback(const void *k1, const void *k2) {
    return strcmp(k1, k2);
}

void freeTestCallback(void *val) {
    zfree(val);
}

hashtableType KvstoreHashtableTestType = {
    .hashFunction = hashTestCallback,
    .keyCompare = cmpTestCallback,
    .entryDestructor = freeTestCallback,
    .rehashingStarted = kvstoreHashtableRehashingStarted,
    .rehashingCompleted = kvstoreHashtableRehashingCompleted,
    .trackMemUsage = kvstoreHashtableTrackMemUsage,
    .getMetadataSize = kvstoreHashtableMetadataSize,
};

char *stringFromInt(int value) {
    char buf[32];
    int len;
    char *s;

    len = snprintf(buf, sizeof(buf), "%d", value);
    s = zmalloc(len + 1);
    memcpy(s, buf, len);
    s[len] = '\0';
    return s;
}

int test_kvstoreAdd16Keys(int argc, char **argv, int flags) {
    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    int i;

    int didx = 0;
    kvstore *kvs1 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);
    kvstore *kvs2 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND | KVSTORE_FREE_EMPTY_HASHTABLES);

    for (i = 0; i < 16; i++) {
        TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
        TEST_ASSERT(kvstoreHashtableAdd(kvs2, didx, stringFromInt(i)));
    }
    TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 16);
    TEST_ASSERT(kvstoreSize(kvs1) == 16);
    TEST_ASSERT(kvstoreHashtableSize(kvs2, didx) == 16);
    TEST_ASSERT(kvstoreSize(kvs2) == 16);

    kvstoreRelease(kvs1);
    kvstoreRelease(kvs2);
    return 0;
}

int test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyHashtable(int argc, char **argv, int flags) {
    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    int i;
    void *key;
    kvstoreIterator *kvs_it;

    int didx = 0;
    int curr_slot = 0;
    kvstore *kvs1 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);

    for (i = 0; i < 16; i++) {
        TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
    }

    kvs_it = kvstoreIteratorInit(kvs1, HASHTABLE_ITER_SAFE);
    while (kvstoreIteratorNext(kvs_it, &key)) {
        curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
        TEST_ASSERT(kvstoreHashtableDelete(kvs1, curr_slot, key));
    }
    kvstoreIteratorRelease(kvs_it);

    hashtable *ht = kvstoreGetHashtable(kvs1, didx);
    TEST_ASSERT(ht != NULL);
    TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
    TEST_ASSERT(kvstoreSize(kvs1) == 0);

    kvstoreRelease(kvs1);
    return 0;
}

int test_kvstoreIteratorRemoveAllKeysDeleteEmptyHashtable(int argc, char **argv, int flags) {
    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    int i;
    void *key;
    kvstoreIterator *kvs_it;

    int didx = 0;
    int curr_slot = 0;
    kvstore *kvs2 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND | KVSTORE_FREE_EMPTY_HASHTABLES);

    for (i = 0; i < 16; i++) {
        TEST_ASSERT(kvstoreHashtableAdd(kvs2, didx, stringFromInt(i)));
    }

    kvs_it = kvstoreIteratorInit(kvs2, HASHTABLE_ITER_SAFE);
    while (kvstoreIteratorNext(kvs_it, &key)) {
        curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
        TEST_ASSERT(kvstoreHashtableDelete(kvs2, curr_slot, key));
    }
    kvstoreIteratorRelease(kvs_it);

    /* Make sure the hashtable was removed from the rehashing list. */
    while (kvstoreIncrementallyRehash(kvs2, 1000)) {
    }

    hashtable *ht = kvstoreGetHashtable(kvs2, didx);
    TEST_ASSERT(ht == NULL);
    TEST_ASSERT(kvstoreHashtableSize(kvs2, didx) == 0);
    TEST_ASSERT(kvstoreSize(kvs2) == 0);

    kvstoreRelease(kvs2);
    return 0;
}

int test_kvstoreHashtableIteratorRemoveAllKeysNoDeleteEmptyHashtable(int argc, char **argv, int flags) {
    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    int i;
    void *key;
    kvstoreHashtableIterator *kvs_di;

    int didx = 0;
    kvstore *kvs1 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);

    for (i = 0; i < 16; i++) {
        TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
    }

    kvs_di = kvstoreGetHashtableIterator(kvs1, didx, HASHTABLE_ITER_SAFE);
    while (kvstoreHashtableIteratorNext(kvs_di, &key)) {
        TEST_ASSERT(kvstoreHashtableDelete(kvs1, didx, key));
    }
    kvstoreReleaseHashtableIterator(kvs_di);

    hashtable *ht = kvstoreGetHashtable(kvs1, didx);
    TEST_ASSERT(ht != NULL);
    TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
    TEST_ASSERT(kvstoreSize(kvs1) == 0);

    kvstoreRelease(kvs1);
    return 0;
}

int test_kvstoreHashtableIteratorRemoveAllKeysDeleteEmptyHashtable(int argc, char **argv, int flags) {
    UNUSED(argc);
    UNUSED(argv);
    UNUSED(flags);

    int i;
    void *key;
    kvstoreHashtableIterator *kvs_di;

    int didx = 0;
    kvstore *kvs2 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND | KVSTORE_FREE_EMPTY_HASHTABLES);

    for (i = 0; i < 16; i++) {
        TEST_ASSERT(kvstoreHashtableAdd(kvs2, didx, stringFromInt(i)));
    }

    kvs_di = kvstoreGetHashtableIterator(kvs2, didx, HASHTABLE_ITER_SAFE);
    while (kvstoreHashtableIteratorNext(kvs_di, &key)) {
        TEST_ASSERT(kvstoreHashtableDelete(kvs2, didx, key));
    }
    kvstoreReleaseHashtableIterator(kvs_di);

    hashtable *ht = kvstoreGetHashtable(kvs2, didx);
    TEST_ASSERT(ht == NULL);
    TEST_ASSERT(kvstoreHashtableSize(kvs2, didx) == 0);
    TEST_ASSERT(kvstoreSize(kvs2) == 0);

    kvstoreRelease(kvs2);
    return 0;
}