/*------------------------------------------------------------------------------
 *
 * Copyright (c) 2011-2025, EURid vzw. All rights reserved.
 * The YADIFA TM software product is provided under the BSD 3-clause license:
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *        * Redistributions of source code must retain the above copyright
 *          notice, this list of conditions and the following disclaimer.
 *        * Redistributions in binary form must reproduce the above copyright
 *          notice, this list of conditions and the following disclaimer in the
 *          documentation and/or other materials provided with the distribution.
 *        * Neither the name of EURid nor the names of its contributors may be
 *          used to endorse or promote products derived from this software
 *          without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *----------------------------------------------------------------------------*/

#include "yatest.h"
#include <dnscore/dnscore.h>
#include <dnscore/format.h>
#include <dnscore/host_address.h>
#include <dnscore/ptr_treemap.h>

#define FIBONACCI_INT64_MAX 92
#define FIBONACCI_INT32_MAX 46
#if __SIZEOF_POINTER__ == 8
#define FIBONACCI_PTR_MAX FIBONACCI_INT64_MAX
#else
#define FIBONACCI_PTR_MAX FIBONACCI_INT32_MAX
#endif

static char  tmp_text_buffer[256];

static void *ptr_key_generator(int64_t value) { return (void *)(intptr_t)value; }

static char *ptr_key_to_text(void *key)
{
    snprintf(tmp_text_buffer, sizeof(tmp_text_buffer), "%p", key);
    return tmp_text_buffer;
}

static void *asciizp_key_generator(int64_t value)
{
    char tmp[24];
    snprintf(tmp, sizeof(tmp), "K%019" PRIi64, value);
    return yatest_strdup(tmp);
}

static char *asciizp_key_to_text(void *key)
{
    if(key != NULL)
    {
        return key;
    }
    else
    {
        strcpy(tmp_text_buffer, "NULL");
        return tmp_text_buffer;
    }
}

static void *dnsname_key_generator(int64_t value)
{
    char tmp[24];
    int  n = snprintf(tmp + 1, sizeof(tmp) - 1, "K%019" PRIi64, value);
    tmp[0] = n;
    return dnsname_dup((uint8_t *)tmp);
}

static char *dnsname_key_to_text(void *key)
{
    if(key != NULL)
    {
        cstr_init_with_dnsname(tmp_text_buffer, key);
    }
    else
    {
        strcpy(tmp_text_buffer, "NULL");
    }
    return tmp_text_buffer;
}

static void *host_address_key_generator(int64_t value)
{
    host_address_t *ha = host_address_new_instance();
    ha->ip.v6.lohi[0] = 0;
    ha->ip.v6.lohi[1] = bswap_64(value);
    ha->ip.v6.bytes[0] = 0x20;
    ha->ip.v6.bytes[1] = 0x02;
    ha->version = 6;
    ha->port = 0;
    return ha;
}

static char *host_address_key_to_text(void *key)
{
    host_address_t *ha = key;
    host_address_to_str(ha, tmp_text_buffer, sizeof(tmp_text_buffer), 0);
    return tmp_text_buffer;
}

static void *socketaddress_key_generator(int64_t value)
{
    uint64_t         addr[2] = {bswap_64(0x2002000000000000ULL), bswap_64(value)};
    socketaddress_t *sa = yatest_malloc(sizeof(socketaddress_t));
    memset(sa, 0, sizeof(socketaddress_t));
    sa->sa6.sin6_family = AF_INET6;
    memcpy(&sa->sa6.sin6_addr, addr, 16);
    return sa;
}

static char *socketaddress_key_to_text(void *key)
{
    snformat(tmp_text_buffer, sizeof(tmp_text_buffer), "%{sockaddr}", key);
    return tmp_text_buffer;
}

static int64_t fibonacci(int64_t value)
{
    if(value < 2)
    {
        return value;
    }
    static int64_t *fibonacci_memorised = NULL;
    if(fibonacci_memorised == NULL)
    {
        fibonacci_memorised = yatest_malloc((FIBONACCI_PTR_MAX + 1) * sizeof(int64_t));
        fibonacci_memorised[0] = 0;
        fibonacci_memorised[1] = 1;
        for(int64_t i = 2; i <= FIBONACCI_PTR_MAX; ++i)
        {
            fibonacci_memorised[i] = fibonacci_memorised[i - 1] + fibonacci_memorised[i - 2];
        }
    }
    if(value < FIBONACCI_PTR_MAX)
    {
        return fibonacci_memorised[value];
    }
    else // beyond 64 bits anyway ...
    {
        return INT64_MAX;
    }
}

static int add_del_test_common(ptr_treemap_node_compare_t *comparator, void *(*key_generator)(int64_t), char *(*key_to_text)(void *), bool is_nullable)
{
    dnscore_init();

    ptr_treemap_t tree;
    tree.root = NULL;
    tree.compare = comparator;

    for(int64_t i = 2; i <= FIBONACCI_PTR_MAX; ++i)
    {
        char *key = key_generator(fibonacci(i));
        yatest_log("inserting key: %s (%lli)", key_to_text(key), i);
        /*ptr_treemap_node_t *node = */ ptr_treemap_insert(&tree, key);
    }

    if(is_nullable)
    {
        /*ptr_treemap_node_t *node = */ ptr_treemap_insert(&tree, NULL);
    }

    ptr_treemap_iterator_t iter;
    ptr_treemap_iterator_init(&tree, &iter);
    while(ptr_treemap_iterator_hasnext(&iter))
    {
        ptr_treemap_node_t *node = ptr_treemap_iterator_next_node(&iter);
        yatest_log("iterated: %s", key_to_text(node->key));
    }

    if(!is_nullable)
    {
        ptr_treemap_iterator_init(&tree, &iter);
        for(;;)
        {
            void *key = ptr_treemap_iterator_hasnext_next_value(&iter);
            if(key != NULL)
            {
                yatest_log("iterated (bis): %s", key_to_text(key));
                continue;
            }
            break;
        }
    }

    for(int64_t i = 2; i <= FIBONACCI_PTR_MAX; ++i)
    {
        char *key = key_generator(fibonacci(i));
        yatest_log("finding key: %s", key_to_text(key));
        ptr_treemap_node_t *node = ptr_treemap_find(&tree, key);
        if(node == NULL)
        {
            yatest_err("expected to find node for key %s", key_to_text(key));

            ptr_treemap_iterator_init(&tree, &iter);
            while(ptr_treemap_iterator_hasnext(&iter))
            {
                ptr_treemap_node_t *node = ptr_treemap_iterator_next_node(&iter);
                yatest_err("iterated: %s", key_to_text(node->key));
            }
            return 1;
        }
        yatest_log("deleting key: %s", key_to_text(key));
        ptr_treemap_delete(&tree, key);
    }

    if(is_nullable)
    {
        ptr_treemap_delete(&tree, NULL);
    }

    if(!ptr_treemap_isempty(&tree))
    {
        yatest_err("expected the treemap to be empty");
        return 1;
    }

    dnscore_finalize();
    return 0;
}

static int add_del_ptr_test() { return add_del_test_common(ptr_treemap_ptr_node_compare, ptr_key_generator, ptr_key_to_text, false); }

static int add_del_asciizp_test() { return add_del_test_common(ptr_treemap_asciizp_node_compare, asciizp_key_generator, asciizp_key_to_text, false); }

static int add_del_asciizcasep_test() { return add_del_test_common(ptr_treemap_asciizcasep_node_compare, asciizp_key_generator, asciizp_key_to_text, false); }

static int add_del_nullable_asciizcasep_test() { return add_del_test_common(ptr_treemap_nullable_asciizp_node_compare, asciizp_key_generator, asciizp_key_to_text, true); }

static int add_del_dnsname_test() { return add_del_test_common(ptr_treemap_dnsname_node_compare, dnsname_key_generator, dnsname_key_to_text, false); }

static int add_del_dnslabel_test() { return add_del_test_common(ptr_treemap_dnslabel_node_compare, dnsname_key_generator, dnsname_key_to_text, false); }

static int add_del_nullable_dnsname_test() { return add_del_test_common(ptr_treemap_nullable_dnsname_node_compare, dnsname_key_generator, dnsname_key_to_text, true); }

static int add_del_fqdn_test() { return add_del_test_common(ptr_treemap_fqdn_node_compare, dnsname_key_generator, dnsname_key_to_text, false); }

static int add_del_host_address_test() { return add_del_test_common(ptr_treemap_host_address_node_compare, host_address_key_generator, host_address_key_to_text, true); }

static int add_del_socketaddress_test() { return add_del_test_common(ptr_treemap_socketaddress_node_compare, socketaddress_key_generator, socketaddress_key_to_text, false); }

YATEST_TABLE_BEGIN
YATEST(add_del_ptr_test)
YATEST(add_del_asciizp_test)
YATEST(add_del_asciizcasep_test)
YATEST(add_del_nullable_asciizcasep_test)
YATEST(add_del_dnsname_test)
YATEST(add_del_nullable_dnsname_test)
YATEST(add_del_fqdn_test)
YATEST(add_del_dnslabel_test)
YATEST(add_del_host_address_test)
YATEST(add_del_socketaddress_test)
YATEST_TABLE_END
