/*
 * SPDX-FileCopyrightText: 2014-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

/* Test infolist functions */

#include "CppUTest/TestHarness.h"
#include "string.h"

extern "C"
{
#include <stdlib.h>
#include "src/core/core-hook.h"
#include "src/core/core-infolist.h"
}

struct t_hook *hook_test_infolist = NULL;


TEST_GROUP(CoreInfolist)
{
    /*
     * Callback for the infolist used in tests.
     */

    static struct t_infolist *
    test_infolist_cb (const void *pointer, void *data,
                      const char *infolist_name, void *obj_pointer,
                      const char *arguments)
    {
        struct t_infolist *ptr_infolist;
        struct t_infolist_item *ptr_item;
        static const char buffer[4] = { 'a', 'b', 'c', 0 };

        /* Make C++ compiler happy. */
        (void) pointer;
        (void) data;
        (void) infolist_name;
        (void) obj_pointer;

        ptr_infolist = infolist_new (NULL);
        if (!ptr_infolist)
            return NULL;

        ptr_item = infolist_new_item (ptr_infolist);
        if (!ptr_item)
            goto error;

        if (!infolist_new_var_integer (ptr_item, "integer", 123456))
            goto error;
        if (!infolist_new_var_string (ptr_item, "string", "test string"))
            goto error;
        if (!infolist_new_var_pointer (ptr_item, "pointer", (void *)0x123abc))
            goto error;
        if (!infolist_new_var_buffer (ptr_item, "buffer", (void *)buffer,
                                      sizeof (buffer)))
            goto error;
        if (!infolist_new_var_time (ptr_item, "time", 1234567890))
            goto error;
        if (!infolist_new_var_long (ptr_item, "long", 2123456789))
            goto error;
        if (!infolist_new_var_longlong (ptr_item, "longlong", 9123456789123456789))
            goto error;

        if (arguments && (strcmp (arguments, "test2") == 0))
        {
            ptr_item = infolist_new_item (ptr_infolist);
            if (!ptr_item)
                goto error;

            if (!infolist_new_var_string (ptr_item, "string2", "test2"))
                goto error;
        }

        return ptr_infolist;

    error:
        infolist_free (ptr_infolist);
        return NULL;
    }

    void setup()
    {
        hook_test_infolist = hook_infolist (
            NULL, "infolist_test", "Test infolist", NULL, "test",
            &test_infolist_cb, NULL, NULL);
    }

    void teardown()
    {
        unhook (hook_test_infolist);
    }
};

/*
 * Test functions:
 *   infolist_new
 *   infolist_new_item
 *   infolist_new_var_integer
 *   infolist_new_var_string
 *   infolist_new_var_pointer
 *   infolist_new_var_buffer
 *   infolist_new_var_time
 */

TEST(CoreInfolist, New)
{
    struct t_infolist *infolist;
    struct t_infolist_item *item;
    struct t_infolist_var *var_int, *var_str, *var_ptr, *var_buf, *var_time;
    struct t_infolist_var *var_long, *var_longlong;
    const char buffer[3] = { 12, 34, 56 };

    /* Create a new infolist. */
    infolist = infolist_new(NULL);
    CHECK(infolist);

    /* Check initial infolist values. */
    POINTERS_EQUAL(NULL, infolist->plugin);
    POINTERS_EQUAL(NULL, infolist->items);
    POINTERS_EQUAL(NULL, infolist->last_item);
    POINTERS_EQUAL(NULL, infolist->ptr_item);

    /* Check that the infolist is the last one in list. */
    POINTERS_EQUAL(last_weechat_infolist, infolist);

    /* Create a new item in infolist. */
    item = infolist_new_item (infolist);
    CHECK(item);

    /* Check initial item values. */
    POINTERS_EQUAL(NULL, item->vars);
    POINTERS_EQUAL(NULL, item->last_var);
    STRCMP_EQUAL(NULL, item->fields);
    POINTERS_EQUAL(NULL, item->prev_item);
    POINTERS_EQUAL(NULL, item->next_item);

    /* Check that item is in infolist. */
    POINTERS_EQUAL(item, infolist->items);
    POINTERS_EQUAL(item, infolist->last_item);

    /* Test integer variable. */
    var_int = infolist_new_var_integer (item, "test_integer", 123456);
    CHECK(var_int);
    STRCMP_EQUAL("test_integer", var_int->name);
    LONGS_EQUAL(INFOLIST_INTEGER, var_int->type);
    LONGS_EQUAL(123456, *((int *)var_int->value));
    LONGS_EQUAL(0, var_int->size);
    POINTERS_EQUAL(NULL, var_int->prev_var);
    POINTERS_EQUAL(NULL, var_int->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_int, item->last_var);

    /* Test string variable. */
    var_str = infolist_new_var_string (item, "test_string", "abc");
    CHECK(var_str);
    STRCMP_EQUAL("test_string", var_str->name);
    LONGS_EQUAL(INFOLIST_STRING, var_str->type);
    STRCMP_EQUAL("abc", (const char *)var_str->value);
    LONGS_EQUAL(0, var_str->size);
    POINTERS_EQUAL(var_int, var_str->prev_var);
    POINTERS_EQUAL(NULL, var_str->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_str, item->last_var);

    /* Test pointer variable. */
    var_ptr = infolist_new_var_pointer (item, "test_pointer",
                                        (void *)0x123abc);
    CHECK(var_ptr);
    STRCMP_EQUAL("test_pointer", var_ptr->name);
    LONGS_EQUAL(INFOLIST_POINTER, var_ptr->type);
    POINTERS_EQUAL(0x123abc, var_ptr->value);
    LONGS_EQUAL(0, var_ptr->size);
    POINTERS_EQUAL(var_str, var_ptr->prev_var);
    POINTERS_EQUAL(NULL, var_ptr->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_ptr, item->last_var);

    /* Test buffer variable. */
    var_buf = infolist_new_var_buffer (item, "test_buffer", (void *)buffer, 3);
    CHECK(var_buf);
    STRCMP_EQUAL("test_buffer", var_buf->name);
    LONGS_EQUAL(INFOLIST_BUFFER, var_buf->type);
    LONGS_EQUAL(12, ((char *)var_buf->value)[0]);
    LONGS_EQUAL(34, ((char *)var_buf->value)[1]);
    LONGS_EQUAL(56, ((char *)var_buf->value)[2]);
    LONGS_EQUAL(3, var_buf->size);
    POINTERS_EQUAL(var_ptr, var_buf->prev_var);
    POINTERS_EQUAL(NULL, var_buf->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_buf, item->last_var);

    /* Test time variable. */
    var_time = infolist_new_var_time (item, "test_time", 1234567890);
    CHECK(var_time);
    STRCMP_EQUAL("test_time", var_time->name);
    LONGS_EQUAL(INFOLIST_TIME, var_time->type);
    LONGS_EQUAL(1234567890, *((time_t *)var_time->value));
    LONGS_EQUAL(0, var_time->size);
    POINTERS_EQUAL(var_buf, var_time->prev_var);
    POINTERS_EQUAL(NULL, var_time->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_time, item->last_var);

    /* Test long variable. */
    var_long = infolist_new_var_long (item, "test_long", 2123456789);
    CHECK(var_long);
    STRCMP_EQUAL("test_long", var_long->name);
    LONGS_EQUAL(INFOLIST_LONG, var_long->type);
    LONGS_EQUAL(2123456789, *((long *)var_long->value));
    LONGS_EQUAL(0, var_long->size);
    POINTERS_EQUAL(var_time, var_long->prev_var);
    POINTERS_EQUAL(NULL, var_long->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_long, item->last_var);

    /* Test long long variable. */
    var_longlong = infolist_new_var_longlong (item, "test_longlong", 9123456789123456789);
    CHECK(var_longlong);
    STRCMP_EQUAL("test_longlong", var_longlong->name);
    LONGS_EQUAL(INFOLIST_LONGLONG, var_longlong->type);
    CHECK(9123456789123456789 == *((long long *)var_longlong->value));
    LONGS_EQUAL(0, var_longlong->size);
    POINTERS_EQUAL(var_long, var_longlong->prev_var);
    POINTERS_EQUAL(NULL, var_longlong->next_var);
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_longlong, item->last_var);

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_new_var_integer_take_name_ownership
 *   infolist_new_var_string_take_ownership
 *   infolist_new_var_buffer_take_ownership
 *   infolist_new_var_time_take_name_ownership
 */

TEST(CoreInfolist, NewVarTakeOwnership)
{
    struct t_infolist *infolist;
    struct t_infolist_item *item;
    struct t_infolist_var *var_int, *var_str, *var_buf, *var_time;
    char *int_name, *str_name, *str_value, *buf_name, *buf_value, *time_name;

    infolist = infolist_new (NULL);
    CHECK(infolist);
    item = infolist_new_item (infolist);
    CHECK(item);

    /*
     * Error cases: with invalid arguments the functions must return NULL and
     * free the name (and, for string/buffer, the value/pointer) they were
     * given (checked by ASAN/valgrind); heap-allocated name and value are
     * passed on purpose to exercise those frees.
     */
    POINTERS_EQUAL(NULL, infolist_new_var_integer_take_name_ownership (
                             NULL, strdup ("test"), 123));
    POINTERS_EQUAL(NULL, infolist_new_var_integer_take_name_ownership (
                             item, strdup (""), 123));
    POINTERS_EQUAL(NULL, infolist_new_var_string_take_ownership (
                             NULL, strdup ("test"), strdup ("abc")));
    POINTERS_EQUAL(NULL, infolist_new_var_string_take_ownership (
                             item, NULL, strdup ("abc")));
    POINTERS_EQUAL(NULL, infolist_new_var_string_take_ownership (
                             item, strdup (""), strdup ("abc")));
    POINTERS_EQUAL(NULL, infolist_new_var_buffer_take_ownership (
                             NULL, strdup ("test"), malloc (3), 3));
    POINTERS_EQUAL(NULL, infolist_new_var_buffer_take_ownership (
                             item, NULL, malloc (3), 3));
    POINTERS_EQUAL(NULL, infolist_new_var_buffer_take_ownership (
                             item, strdup (""), malloc (3), 3));
    POINTERS_EQUAL(NULL, infolist_new_var_buffer_take_ownership (
                             item, strdup ("test"), malloc (3), 0));
    POINTERS_EQUAL(NULL, infolist_new_var_buffer_take_ownership (
                             item, strdup ("test"), malloc (3), -1));
    POINTERS_EQUAL(NULL, infolist_new_var_time_take_name_ownership (
                             NULL, strdup ("test"), 456));
    POINTERS_EQUAL(NULL, infolist_new_var_time_take_name_ownership (
                             item, strdup (""), 456));

    /* Add an integer variable, taking ownership of the name. */
    int_name = strdup ("test_integer");
    var_int = infolist_new_var_integer_take_name_ownership (item, int_name,
                                                            123);
    CHECK(var_int);
    /* The name pointer is stored as-is, not copied. */
    POINTERS_EQUAL(int_name, var_int->name);
    STRCMP_EQUAL("test_integer", var_int->name);
    LONGS_EQUAL(INFOLIST_INTEGER, var_int->type);
    LONGS_EQUAL(123, *((int *)var_int->value));

    /* Add a string variable, taking ownership of the name and the value. */
    str_name = strdup ("test_string");
    str_value = strdup ("test ownership");
    var_str = infolist_new_var_string_take_ownership (item, str_name,
                                                      str_value);
    CHECK(var_str);
    /* The name and value pointers are stored as-is, not copied. */
    POINTERS_EQUAL(str_name, var_str->name);
    STRCMP_EQUAL("test_string", var_str->name);
    LONGS_EQUAL(INFOLIST_STRING, var_str->type);
    POINTERS_EQUAL(str_value, var_str->value);
    STRCMP_EQUAL("test ownership", (const char *)var_str->value);
    LONGS_EQUAL(0, var_str->size);

    /* Add a buffer variable, taking ownership of the name and the pointer. */
    buf_name = strdup ("test_buffer");
    buf_value = (char *)malloc (3);
    buf_value[0] = 12;
    buf_value[1] = 34;
    buf_value[2] = 56;
    var_buf = infolist_new_var_buffer_take_ownership (item, buf_name,
                                                      buf_value, 3);
    CHECK(var_buf);
    /* The name and pointer are stored as-is, not copied. */
    POINTERS_EQUAL(buf_name, var_buf->name);
    STRCMP_EQUAL("test_buffer", var_buf->name);
    LONGS_EQUAL(INFOLIST_BUFFER, var_buf->type);
    POINTERS_EQUAL(buf_value, var_buf->value);
    LONGS_EQUAL(12, ((char *)var_buf->value)[0]);
    LONGS_EQUAL(34, ((char *)var_buf->value)[1]);
    LONGS_EQUAL(56, ((char *)var_buf->value)[2]);
    LONGS_EQUAL(3, var_buf->size);

    /* Add a time variable, taking ownership of the name. */
    time_name = strdup ("test_time");
    var_time = infolist_new_var_time_take_name_ownership (item, time_name,
                                                          1234567890);
    CHECK(var_time);
    /* The name pointer is stored as-is, not copied. */
    POINTERS_EQUAL(time_name, var_time->name);
    STRCMP_EQUAL("test_time", var_time->name);
    LONGS_EQUAL(INFOLIST_TIME, var_time->type);
    LONGS_EQUAL(1234567890, *((time_t *)var_time->value));

    /* Check that variables are chained in the item, first to last. */
    POINTERS_EQUAL(var_int, item->vars);
    POINTERS_EQUAL(var_time, item->last_var);

    /* Infolist_free frees the owned names and the string/buffer values. */
    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_valid
 *   infolist_free
 */

TEST(CoreInfolist, Valid)
{
    struct t_infolist *infolist;

    LONGS_EQUAL(0, infolist_valid (NULL));
    LONGS_EQUAL(0, infolist_valid ((struct t_infolist *)0x1));

    infolist = infolist_new (NULL);
    LONGS_EQUAL(1, infolist_valid (infolist));

    infolist_free (infolist);

    LONGS_EQUAL(0, infolist_valid (infolist));

    /* Test free of NULL infolist. */
    infolist_free (NULL);
}

/*
 * Test functions:
 *   infolist_search_var
 */

TEST(CoreInfolist, Search)
{
    struct t_infolist *infolist;
    struct t_infolist_item *ptr_item;
    struct t_infolist_var *ptr_var;

    infolist = hook_infolist_get (NULL, "infolist_test", NULL, "test2");

    /* Move to first item in infolist. */
    ptr_item = infolist->items;
    POINTERS_EQUAL(ptr_item, infolist_next (infolist));

    /* Search the first variable. */
    ptr_var = infolist_search_var (infolist, "integer");
    POINTERS_EQUAL(ptr_item->vars, ptr_var);

    /* Search the second variable. */
    ptr_var = infolist_search_var (infolist, "string");
    POINTERS_EQUAL(ptr_item->vars->next_var, ptr_var);

    /* Search an unknown variable. */
    ptr_var = infolist_search_var (infolist, "string2");
    POINTERS_EQUAL(NULL, ptr_var);

    /* Move to second item in infolist. */
    ptr_item = ptr_item->next_item;
    POINTERS_EQUAL(ptr_item, infolist_next (infolist));

    /* Search the first variable. */
    ptr_var = infolist_search_var (infolist, "string2");
    POINTERS_EQUAL(ptr_item->vars, ptr_var);

    /* Search an unknown variable. */
    ptr_var = infolist_search_var (infolist, "string3");
    POINTERS_EQUAL(NULL, ptr_var);

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_next
 *   infolist_prev
 *   infolist_reset_item_cursor
 */

TEST(CoreInfolist, Move)
{
    struct t_infolist *infolist;

    infolist = hook_infolist_get (NULL, "infolist_test", NULL, "test2");

    POINTERS_EQUAL(NULL, infolist->ptr_item);

    infolist_next (NULL);
    infolist_prev (NULL);
    infolist_reset_item_cursor (NULL);

    /* Move to first item in infolist. */
    POINTERS_EQUAL(infolist->items, infolist_next (infolist));
    POINTERS_EQUAL(infolist->items, infolist->ptr_item);

    /* Reset item cursor. */
    infolist_reset_item_cursor (infolist);
    POINTERS_EQUAL(NULL, infolist->ptr_item);

    infolist_next (infolist);

    /* Move to second item in infolist. */
    POINTERS_EQUAL(infolist->items->next_item, infolist_next (infolist));
    POINTERS_EQUAL(infolist->items->next_item, infolist->ptr_item);

    /* Move back to first item in infolist. */
    POINTERS_EQUAL(infolist->items, infolist_prev (infolist));
    POINTERS_EQUAL(infolist->items, infolist->ptr_item);

    /* Move before first item in infolist. */
    POINTERS_EQUAL(NULL, infolist_prev (infolist));
    POINTERS_EQUAL(NULL, infolist->ptr_item);

    /* Move after second item in infolist. */
    infolist_next (infolist);
    infolist_next (infolist);
    POINTERS_EQUAL(NULL, infolist_next (infolist));
    POINTERS_EQUAL(NULL, infolist->ptr_item);

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_get
 */

TEST(CoreInfolist, Get)
{
    struct t_infolist *infolist;
    struct t_infolist_var *ptr_var;

    /* Get an infolist with one item. */
    infolist = hook_infolist_get (NULL, "infolist_test", NULL, NULL);
    CHECK(infolist);

    /* Check that there is only one item. */
    CHECK(infolist->items);
    POINTERS_EQUAL(NULL, infolist->items->next_item);

    infolist_free (infolist);

    /* Get an infolist with two items. */
    infolist = hook_infolist_get (NULL, "infolist_test", NULL, "test2");
    CHECK(infolist);

    /* Check that there are exactly two items. */
    CHECK(infolist->items);
    CHECK(infolist->items->next_item);
    POINTERS_EQUAL(NULL, infolist->items->next_item->next_item);

    /* Check variables in first item. */
    ptr_var = infolist->items->vars;
    STRCMP_EQUAL("integer", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("string", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("pointer", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("buffer", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("time", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("long", ptr_var->name);
    ptr_var = ptr_var->next_var;
    STRCMP_EQUAL("longlong", ptr_var->name);
    LONGS_EQUAL(NULL, ptr_var->next_var);

    /* Check variables in second item. */
    ptr_var = infolist->items->next_item->vars;
    STRCMP_EQUAL("string2", ptr_var->name);
    LONGS_EQUAL(NULL, ptr_var->next_var);

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_integer
 *   infolist_string
 *   infolist_pointer
 *   infolist_buffer
 *   infolist_time
 */

TEST(CoreInfolist, GetValues)
{
    struct t_infolist *infolist;
    void *ptr_buffer;
    int size;

    infolist = hook_infolist_get (NULL, "infolist_test", NULL, "test2");
    CHECK(infolist);

    infolist_next (infolist);

    LONGS_EQUAL(123456, infolist_integer (infolist, "integer"));
    STRCMP_EQUAL("test string", infolist_string (infolist, "string"));
    LONGS_EQUAL(0x123abc, infolist_pointer (infolist, "pointer"));
    ptr_buffer = infolist_buffer (infolist, "buffer", &size);
    LONGS_EQUAL(4, size);
    LONGS_EQUAL('a', ((const char *)ptr_buffer)[0]);
    LONGS_EQUAL('b', ((const char *)ptr_buffer)[1]);
    LONGS_EQUAL('c', ((const char *)ptr_buffer)[2]);
    LONGS_EQUAL(0, ((const char *)ptr_buffer)[3]);
    LONGS_EQUAL(1234567890, infolist_time (infolist, "time"));

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_fields
 */

TEST(CoreInfolist, Fields)
{
    struct t_infolist *infolist;
    const char *fields1 = "i:integer,s:string,p:pointer,b:buffer,t:time,l:long,L:longlong";
    const char *fields2 = "s:string2";

    infolist = hook_infolist_get (NULL, "infolist_test", NULL, "test2");
    CHECK(infolist);

    /* Check fields in first item. */
    infolist_next (infolist);
    LONGS_EQUAL(NULL, infolist->items->fields);
    STRCMP_EQUAL(fields1, infolist_fields (infolist));
    STRCMP_EQUAL(fields1, infolist->items->fields);

    /* Check fields in second item. */
    infolist_next (infolist);
    LONGS_EQUAL(NULL, infolist->items->next_item->fields);
    STRCMP_EQUAL(fields2, infolist_fields (infolist));
    STRCMP_EQUAL(fields2, infolist->items->next_item->fields);

    infolist_free (infolist);
}

/*
 * Test functions:
 *   infolist_print_log
 */

TEST(CoreInfolist, PrintLog)
{
    /* TODO: write tests */
}
