
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


static njs_int_t
lvlhsh_unit_test_key_test(njs_lvlhsh_query_t *lhq, void *data)
{
    if (*(uintptr_t *) lhq->key.start == *(uintptr_t *) data) {
        return NJS_OK;
    }

    return NJS_DECLINED;
}


static void *
lvlhsh_unit_test_pool_alloc(void *pool, size_t size)
{
    return njs_mp_align(pool, NJS_MAX_ALIGNMENT, size);
}


static void
lvlhsh_unit_test_pool_free(void *pool, void *p, size_t size)
{
    njs_mp_free(pool, p);
}


static const njs_lvlhsh_proto_t  lvlhsh_proto  njs_aligned(64) = {
    NJS_LVLHSH_LARGE_SLAB,
    lvlhsh_unit_test_key_test,
    lvlhsh_unit_test_pool_alloc,
    lvlhsh_unit_test_pool_free,
};


static njs_int_t
lvlhsh_unit_test_add(njs_lvlhsh_t *lh, const njs_lvlhsh_proto_t *proto,
    void *pool, uintptr_t key)
{
    njs_lvlhsh_query_t  lhq;

    lhq.key_hash = key;
    lhq.replace = 0;
    lhq.key.length = sizeof(uintptr_t);
    lhq.key.start = (u_char *) &key;
    lhq.proto = proto;
    lhq.pool = pool;

    switch (njs_lvlhsh_insert(lh, &lhq)) {

    case NJS_OK:
        ((njs_flathsh_elt_t *) lhq.value)->value[0] = (void *) key;
        return NJS_OK;

    case NJS_DECLINED:
        njs_printf("lvlhsh unit test failed: key %08Xl is already in hash\n",
                   (long) key);
        /* Fall through. */

    default:
        return NJS_ERROR;
    }
}


static njs_int_t
lvlhsh_unit_test_get(njs_lvlhsh_t *lh, const njs_lvlhsh_proto_t *proto,
    uintptr_t key)
{
    njs_lvlhsh_query_t  lhq;

    lhq.key_hash = key;
    lhq.key.length = sizeof(uintptr_t);
    lhq.key.start = (u_char *) &key;
    lhq.proto = proto;

    if (njs_lvlhsh_find(lh, &lhq) == NJS_OK) {

        if (key == (uintptr_t) ((njs_flathsh_elt_t *) lhq.value)->value[0]) {
            return NJS_OK;
        }
    }

    njs_printf("lvlhsh unit test failed: key %08Xl not found in hash\n",
               (long) key);

    return NJS_ERROR;
}


static njs_int_t
lvlhsh_unit_test_delete(njs_lvlhsh_t *lh, const njs_lvlhsh_proto_t *proto,
    void *pool, uintptr_t key)
{
    njs_int_t           ret;
    njs_lvlhsh_query_t  lhq;

    lhq.key_hash = key;
    lhq.key.length = sizeof(uintptr_t);
    lhq.key.start = (u_char *) &key;
    lhq.proto = proto;
    lhq.pool = pool;

    ret = njs_lvlhsh_delete(lh, &lhq);

    if (ret != NJS_OK) {
        njs_printf("lvlhsh unit test failed: key %08lX not found in hash\n",
               (long) key);
    }

    return ret;
}


static njs_int_t
lvlhsh_unit_test(njs_uint_t n)
{
    njs_mp_t           *pool;
    uint32_t           key;
    njs_uint_t         i;
    njs_lvlhsh_t       lh;
    njs_lvlhsh_each_t  lhe;

    const size_t       min_chunk_size = 32;
    const size_t       page_size = 1024;
    const size_t       page_alignment = 128;
    const size_t       cluster_size = 4096;

    pool = njs_mp_create(cluster_size, page_alignment, page_size,
                         min_chunk_size);
    if (pool == NULL) {
        return NJS_ERROR;
    }

    njs_printf("lvlhsh unit test started: %l items\n", (long) n);

    njs_memzero(&lh, sizeof(njs_lvlhsh_t));

    key = 0;
    for (i = 0; i < n; i++) {
        key = njs_murmur_hash2(&key, sizeof(uint32_t));

        if (lvlhsh_unit_test_add(&lh, &lvlhsh_proto, pool, key) != NJS_OK) {
            njs_printf("lvlhsh add unit test failed at %l\n", (long) i);
            return NJS_ERROR;
        }
    }

    key = 0;
    for (i = 0; i < n; i++) {
        key = njs_murmur_hash2(&key, sizeof(uint32_t));

        if (lvlhsh_unit_test_get(&lh, &lvlhsh_proto, key) != NJS_OK) {
            return NJS_ERROR;
        }
    }

    njs_lvlhsh_each_init(&lhe, &lvlhsh_proto);

    for (i = 0; i < n + 1; i++) {
        if (njs_lvlhsh_each(&lh, &lhe) == NULL) {
            break;
        }
    }

    if (i != n) {
        njs_printf("lvlhsh each unit test failed at %l of %l\n",
                   (long) i, (long) n);
        return NJS_ERROR;
    }

    key = 0;
    for (i = 0; i < n; i++) {
        key = njs_murmur_hash2(&key, sizeof(uint32_t));

        if (lvlhsh_unit_test_delete(&lh, &lvlhsh_proto, pool, key) != NJS_OK) {
            return NJS_ERROR;
        }
    }

    if (!njs_mp_is_empty(pool)) {
        njs_printf("mem cache pool is not empty\n");
        return NJS_ERROR;
    }

    njs_mp_destroy(pool);

    njs_printf("lvlhsh unit test passed\n");

    return NJS_OK;
}


int
main(void)
{
     return lvlhsh_unit_test(1000 * 1000);
}
