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

#include <nxt_main.h>
#include "nxt_tests.h"
#include "nxt_rbtree1.h"


#define                                                                       \
nxt_rbtree1_is_empty(tree)                                                    \
    (((tree)->root) == (tree)->sentinel)


#define                                                                       \
nxt_rbtree1_is_there_successor(tree, node)                                    \
    ((node) != (tree)->sentinel)


nxt_inline nxt_rbtree1_node_t *
nxt_rbtree1_node_successor(nxt_rbtree1_t *tree, nxt_rbtree1_node_t *node)
{
    nxt_rbtree1_node_t  *parent;

    if (node->right != tree->sentinel) {
        return nxt_rbtree1_min(node->right, tree->sentinel);
    }

    for ( ;; ) {
        parent = node->parent;

        if (parent == NULL) {
            return tree->sentinel;
        }

        if (node == parent->left) {
            return parent;
        }

        node = parent;
    }
}


static void nxt_rbtree1_test_insert_value(nxt_rbtree1_node_t *temp,
    nxt_rbtree1_node_t *node, nxt_rbtree1_node_t *sentinel);
static nxt_int_t nxt_rbtree1_test_compare(nxt_rbtree1_node_t *node1,
    nxt_rbtree1_node_t *node2);
static int nxt_cdecl nxt_rbtree1_test_sort_cmp(const void *one,
    const void *two);
static nxt_rbtree1_node_t *nxt_rbtree1_test_find(nxt_rbtree1_t *tree,
    nxt_rbtree1_node_t *node);


nxt_int_t
nxt_rbtree1_test(nxt_thread_t *thr, nxt_uint_t n)
{
    uint32_t            key, *keys;
    nxt_uint_t          i;
    nxt_nsec_t          start, end;
    nxt_rbtree1_t       tree;
    nxt_rbtree1_node_t  *node, *nodes, sentinel;

    nxt_thread_time_update(thr);

    nxt_log_error(NXT_LOG_NOTICE, thr->log, "rbtree1 test started: %ui", n);

    nxt_rbtree1_init(&tree, &sentinel, nxt_rbtree1_test_insert_value);

    nodes = nxt_malloc(n * sizeof(nxt_rbtree1_node_t));
    if (nodes == NULL) {
        return NXT_ERROR;
    }

    keys = nxt_malloc(n * sizeof(uint32_t));
    if (keys == NULL) {
        nxt_free(keys);
        return NXT_ERROR;
    }

    key = 0;

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

        keys[i] = key;
        nodes[i].key = key;
    }

    nxt_qsort(keys, n, sizeof(uint32_t), nxt_rbtree1_test_sort_cmp);

    nxt_thread_time_update(thr);
    start = nxt_thread_monotonic_time(thr);

    for (i = 0; i < n; i++) {
        nxt_rbtree1_insert(&tree, &nodes[i]);
    }

    for (i = 0; i < n; i++) {
        if (nxt_rbtree1_test_find(&tree, &nodes[i]) != &nodes[i]) {
            nxt_log_alert(thr->log, "rbtree1 test failed: %08XD not found",
                          nodes[i].key);
            goto fail;
        }
    }

    i = 0;
    node = nxt_rbtree1_min(tree.root, tree.sentinel);

    while (nxt_rbtree1_is_there_successor(&tree, node)) {

        if (keys[i] != node->key) {
            nxt_log_alert(thr->log, "rbtree1 test failed: %i: %08XD %08XD",
                          i, keys[i], node->key);
            goto fail;
        }

        i++;
        node = nxt_rbtree1_node_successor(&tree, node);
    }

    if (i != n) {
        nxt_log_alert(thr->log, "rbtree1 test failed: %ui", i);
        goto fail;
    }

    for (i = 0; i < n; i++) {
        nxt_rbtree1_delete(&tree, &nodes[i]);
        nxt_memset(&nodes[i], 0xA5, sizeof(nxt_rbtree1_node_t));
    }

    nxt_thread_time_update(thr);
    end = nxt_thread_monotonic_time(thr);

    if (!nxt_rbtree1_is_empty(&tree)) {
        nxt_log_alert(thr->log, "rbtree1 test failed: tree is not empty");
        goto fail;
    }

    nxt_free(keys);
    nxt_free(nodes);

    nxt_log_error(NXT_LOG_NOTICE, thr->log, "rbtree1 test passed %0.3fs",
                  (end - start) / 1000000000.0);

    return NXT_OK;

fail:

    nxt_free(keys);
    nxt_free(nodes);

    return NXT_ERROR;
}


static void
nxt_rbtree1_test_insert_value(nxt_rbtree1_node_t *temp,
    nxt_rbtree1_node_t *node, nxt_rbtree1_node_t *sentinel)
{
    nxt_rbtree1_node_t  **p;

    for ( ;; ) {
        nxt_prefetch(temp->left);
        nxt_prefetch(temp->right);

        p = (node->key < temp->key) ? &temp->left : &temp->right;

        if (*p == sentinel) {
            break;
        }

        temp = *p;
    }

    *p = node;
    node->parent = temp;
    node->left = sentinel;
    node->right = sentinel;
    nxt_rbtree1_red(node);
}


/*
 * Subtraction cannot be used in these comparison functions because the key
 * values are spread uniform in whole 0 .. 2^32 range but are not grouped
 * around some value as timeout values are.
 */

nxt_inline nxt_int_t
nxt_rbtree1_test_compare(nxt_rbtree1_node_t *node1, nxt_rbtree1_node_t *node2)
{
    if (node1->key < node2->key) {
        return -1;
    }

    if (node1->key == node2->key) {
        return 0;
    }

    return 1;
}


static int nxt_cdecl
nxt_rbtree1_test_sort_cmp(const void *one, const void *two)
{
    const uint32_t  *first, *second;

    first = one;
    second = two;

    if (*first < *second) {
        return -1;
    }

    if (*first == *second) {
        return 0;
    }

    return 1;
}


static nxt_rbtree1_node_t *
nxt_rbtree1_test_find(nxt_rbtree1_t *tree, nxt_rbtree1_node_t *node)
{
    nxt_int_t           n;
    nxt_rbtree1_node_t  *next, *sentinel;

    next = tree->root;
    sentinel = tree->sentinel;

    while (next != sentinel) {
        nxt_prefetch(next->left);
        nxt_prefetch(next->right);

        n = nxt_rbtree1_test_compare(node, next);

        if (n < 0) {
            next = next->left;

        } else if (n > 0) {
            next = next->right;

        } else {
            return next;
        }
    }

    return NULL;
}


#if (NXT_TEST_RTDTSC)

#define NXT_RBT_STEP       (21 * nxt_pagesize / 10 / sizeof(nxt_rbtree1_node_t))

static nxt_rbtree1_t       mb_tree;
static nxt_rbtree1_node_t  mb_sentinel;
static nxt_rbtree1_node_t  *mb_nodes;


nxt_int_t
nxt_rbtree1_mb_start(nxt_thread_t *thr)
{
    uint32_t    key;
    uint64_t    start, end;
    nxt_uint_t  i, n;

    n = NXT_RBT_STEP;

    mb_nodes = nxt_malloc(NXT_RBT_NODES * n * sizeof(nxt_rbtree1_node_t));
    if (mb_nodes == NULL) {
        return NXT_ERROR;
    }

    nxt_rbtree1_init(&mb_tree, &mb_sentinel, nxt_rbtree1_test_insert_value);

    key = 0;

    for (i = 0; i < NXT_RBT_NODES; i++) {
        key = nxt_murmur_hash2(&key, sizeof(uint32_t));
        mb_nodes[n * i].key = key;
    }

    for (i = 0; i < NXT_RBT_NODES - 2; i++) {
        nxt_rbtree1_insert(&mb_tree, &mb_nodes[n * i]);
    }

    n *= (NXT_RBT_NODES - 2);

    start = nxt_rdtsc();
    nxt_rbtree1_insert(&mb_tree, &mb_nodes[n]);
    end = nxt_rdtsc();

    nxt_log_error(NXT_LOG_NOTICE, thr->log,
                  "rbtree1 mb cached insert: %L cycles", end - start);

    return NXT_OK;
}


void
nxt_rbtree1_mb_insert(nxt_thread_t *thr)
{
    uint64_t    start, end;
    nxt_uint_t  n;

    n = NXT_RBT_STEP;
    n *= (NXT_RBT_NODES - 1);

    start = nxt_rdtsc();
    nxt_rbtree1_insert(&mb_tree, &mb_nodes[n]);
    end = nxt_rdtsc();

    nxt_log_error(NXT_LOG_NOTICE, thr->log,
                  "rbtree1 mb insert: %L cycles", end - start);
}


void
nxt_rbtree1_mb_delete(nxt_thread_t *thr)
{
    uint64_t    start, end;
    nxt_uint_t  n;

    n = NXT_RBT_STEP;
    n *= (NXT_RBT_NODES / 4 + 1);

    start = nxt_rdtsc();
    nxt_rbtree1_delete(&mb_tree, &mb_nodes[n]);
    end = nxt_rdtsc();

    nxt_log_error(NXT_LOG_NOTICE, thr->log,
                  "rbtree1 mb delete: %L cycles", end - start);

    nxt_free(mb_nodes);
}

#endif
