/*
 * SPDX-FileCopyrightText: 2022-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * This file is part of WeeChat, the extensible chat client.
 *
 * WeeChat is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * WeeChat is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WeeChat.  If not, see <https://www.gnu.org/licenses/>.
 */

/* Custom bar item functions (used by all GUI) */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdlib.h>
#include <stddef.h>
#include <string.h>

#include "../core/weechat.h"
#include "../core/core-config.h"
#include "../core/core-config-file.h"
#include "../core/core-eval.h"
#include "../core/core-hashtable.h"
#include "../core/core-string.h"
#include "../plugins/plugin.h"
#include "gui-bar-item-custom.h"
#include "gui-bar-item.h"


char *gui_bar_item_custom_option_string[GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS] =
{ "conditions", "content" };
char *gui_bar_item_custom_option_default[GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS] =
{ "", "" };


struct t_gui_bar_item_custom *gui_custom_bar_items = NULL;
struct t_gui_bar_item_custom *last_gui_custom_bar_item = NULL;

/* custom bar items used when reading config */
struct t_gui_bar_item_custom *gui_temp_custom_bar_items = NULL;
struct t_gui_bar_item_custom *last_gui_temp_custom_bar_item = NULL;


/*
 * Check if a custom bar item name is valid: it must not have any
 * space/period.
 *
 * Return:
 *   1: name is valid
 *   0: name is invalid
 */

int
gui_bar_item_custom_name_valid (const char *name)
{
    if (!name || !name[0])
        return 0;

    /* no spaces allowed */
    if (strchr (name, ' '))
        return 0;

    /* no periods allowed */
    if (strchr (name, '.'))
        return 0;

    /* name is valid */
    return 1;
}

/*
 * Search for a custom bar item option name.
 *
 * Return index of option in enum t_gui_bar_item_custom_option,
 * -1 if not found.
 */

int
gui_bar_item_custom_search_option (const char *option_name)
{
    int i;

    if (!option_name)
        return -1;

    for (i = 0; i < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS; i++)
    {
        if (strcmp (gui_bar_item_custom_option_string[i], option_name) == 0)
            return i;
    }

    /* custom bar item option not found */
    return -1;
}

/*
 * Search for a custom bar item by name.
 */

struct t_gui_bar_item_custom *
gui_bar_item_custom_search (const char *item_name)
{
    struct t_gui_bar_item_custom *ptr_item;

    if (!item_name || !item_name[0])
        return NULL;

    for (ptr_item = gui_custom_bar_items; ptr_item;
         ptr_item = ptr_item->next_item)
    {
        if (strcmp (ptr_item->name, item_name) == 0)
            return ptr_item;
    }

    /* custom bar item not found */
    return NULL;
}

/*
 * Search for a custom bar item with name of option (like "name.content").
 *
 * Return pointer to custom bar item found, NULL if not found.
 */

struct t_gui_bar_item_custom *
gui_bar_item_custom_search_with_option_name (const char *option_name)
{
    const char *pos_option;
    char *item_name;
    struct t_gui_bar_item_custom *ptr_item;

    if (!option_name)
        return NULL;

    ptr_item = NULL;

    pos_option = strchr (option_name, '.');
    if (pos_option)
    {
        item_name = string_strndup (option_name, pos_option - option_name);
        if (item_name)
        {
            for (ptr_item = gui_custom_bar_items; ptr_item;
                 ptr_item = ptr_item->next_item)
            {
                if (strcmp (ptr_item->name, item_name) == 0)
                    break;
            }
            free (item_name);
        }
    }

    return ptr_item;
}

/*
 * Callback called when option "conditions" or "content" is changed.
 */

void
gui_bar_item_custom_config_change (const void *pointer, void *data,
                                   struct t_config_option *option)
{
    struct t_gui_bar_item_custom *ptr_item;

    /* make C compiler happy */
    (void) pointer;
    (void) data;

    ptr_item = gui_bar_item_custom_search_with_option_name (option->name);
    if (ptr_item)
        gui_bar_item_update (ptr_item->name);
}

/*
 * Create an option for a custom bar item.
 *
 * Return pointer to new option, NULL if error.
 */

struct t_config_option *
gui_bar_item_custom_create_option (const char *item_name, int index_option,
                                   const char *value)
{
    struct t_config_option *ptr_option;
    char *option_name;

    ptr_option = NULL;

    if (string_asprintf (&option_name,
                         "%s.%s",
                         item_name,
                         gui_bar_item_custom_option_string[index_option]) < 0)
    {
        return NULL;
    }

    switch (index_option)
    {
        case GUI_BAR_ITEM_CUSTOM_OPTION_CONDITIONS:
            ptr_option = config_file_new_option (
                weechat_config_file, weechat_config_section_custom_bar_item,
                option_name, "string",
                N_("condition(s) to display the bar item "
                   "(evaluated, see /help eval)"),
                NULL, 0, 0, value, NULL, 0,
                NULL, NULL, NULL,
                &gui_bar_item_custom_config_change, NULL, NULL,
                NULL, NULL, NULL);
            break;
        case GUI_BAR_ITEM_CUSTOM_OPTION_CONTENT:
            ptr_option = config_file_new_option (
                weechat_config_file, weechat_config_section_custom_bar_item,
                option_name, "string",
                N_("content of bar item (evaluated, see /help eval)"),
                NULL, 0, 0, value, NULL, 0,
                NULL, NULL, NULL,
                &gui_bar_item_custom_config_change, NULL, NULL,
                NULL, NULL, NULL);
            break;
        case GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS:
            break;
    }

    free (option_name);

    return ptr_option;
}

/*
 * Create option for a temporary custom bar item (when reading configuration
 * file).
 */

void
gui_bar_item_custom_create_option_temp (struct t_gui_bar_item_custom *temp_item,
                                        int index_option,
                                        const char *value)
{
    struct t_config_option *new_option;

    new_option = gui_bar_item_custom_create_option (temp_item->name,
                                                    index_option,
                                                    value);
    if (new_option
        && (index_option >= 0)
        && (index_option < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS))
    {
        temp_item->options[index_option] = new_option;
    }
}

/*
 * Custom bar item callback.
 */

char *
gui_bar_item_custom_callback (const void *pointer,
                              void *data,
                              struct t_gui_bar_item *item,
                              struct t_gui_window *window,
                              struct t_gui_buffer *buffer,
                              struct t_hashtable *extra_info)
{
    struct t_gui_bar_item_custom *ptr_item;
    struct t_hashtable *pointers, *options;
    const char *ptr_conditions;
    char *result;
    int rc;

    /* make C compiler happy */
    (void) data;
    (void) item;
    (void) extra_info;

    result = NULL;

    ptr_item = (struct t_gui_bar_item_custom *)pointer;
    if (!ptr_item)
        return NULL;

    pointers = hashtable_new (32,
                              WEECHAT_HASHTABLE_STRING,
                              WEECHAT_HASHTABLE_POINTER,
                              NULL,
                              NULL);
    if (pointers)
    {
        hashtable_set (pointers, "window", window);
        hashtable_set (pointers, "buffer", buffer);
    }

    options = hashtable_new (32,
                             WEECHAT_HASHTABLE_STRING,
                             WEECHAT_HASHTABLE_STRING,
                             NULL,
                             NULL);
    if (options)
        hashtable_set (options, "type", "condition");

    /* check conditions */
    ptr_conditions = CONFIG_STRING(ptr_item->options[GUI_BAR_ITEM_CUSTOM_OPTION_CONDITIONS]);
    if (ptr_conditions && ptr_conditions[0])
    {
        result = eval_expression (
            CONFIG_STRING(ptr_item->options[GUI_BAR_ITEM_CUSTOM_OPTION_CONDITIONS]),
            pointers, NULL, options);
        rc = eval_is_true (result);
        if (result)
        {
            free (result);
            result = NULL;
        }
        if (!rc)
            goto end;
    }

    /* evaluate content */
    result = eval_expression (
        CONFIG_STRING(ptr_item->options[GUI_BAR_ITEM_CUSTOM_OPTION_CONTENT]),
        pointers, NULL, NULL);

end:
    hashtable_free (pointers);
    hashtable_free (options);

    return result;
}

/*
 * Allocate and initialize new custom bar item structure.
 *
 * Return pointer to new custom bar item, NULL if error.
 */

struct t_gui_bar_item_custom *
gui_bar_item_custom_alloc (const char *name)
{
    struct t_gui_bar_item_custom *new_bar_item_custom;
    int i;

    new_bar_item_custom = malloc (sizeof (*new_bar_item_custom));
    if (!new_bar_item_custom)
        return NULL;

    new_bar_item_custom->name = strdup (name);
    for (i = 0; i < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS; i++)
    {
        new_bar_item_custom->options[i] = NULL;
    }
    new_bar_item_custom->bar_item = NULL;
    new_bar_item_custom->prev_item = NULL;
    new_bar_item_custom->next_item = NULL;

    return new_bar_item_custom;
}

/*
 * Create bar item in a custom bar item.
 */

void
gui_bar_item_custom_create_bar_item (struct t_gui_bar_item_custom *item)
{
    gui_bar_item_free (item->bar_item);
    item->bar_item = gui_bar_item_new (
        NULL,
        item->name,
        &gui_bar_item_custom_callback,
        item,
        NULL);
}

/*
 * Create a new custom bar item with options.
 *
 * Return pointer to new bar, NULL if error.
 */

struct t_gui_bar_item_custom *
gui_bar_item_custom_new_with_options (const char *name,
                                      struct t_config_option *conditions,
                                      struct t_config_option *content)
{
    struct t_gui_bar_item_custom *new_bar_item_custom;

    /* create custom bar item */
    new_bar_item_custom = gui_bar_item_custom_alloc (name);
    if (!new_bar_item_custom)
        return NULL;

    new_bar_item_custom->options[GUI_BAR_ITEM_CUSTOM_OPTION_CONDITIONS] = conditions;
    new_bar_item_custom->options[GUI_BAR_ITEM_CUSTOM_OPTION_CONTENT] = content;
    new_bar_item_custom->bar_item = NULL;

    /* add custom bar item to custom bar items queue */
    new_bar_item_custom->prev_item = last_gui_custom_bar_item;
    if (last_gui_custom_bar_item)
        last_gui_custom_bar_item->next_item = new_bar_item_custom;
    else
        gui_custom_bar_items = new_bar_item_custom;
    last_gui_custom_bar_item = new_bar_item_custom;
    new_bar_item_custom->next_item = NULL;

    return new_bar_item_custom;
}

/*
 * Create a new custom bar item.
 *
 * Return pointer to new custom bar item, NULL if not found.
 */

struct t_gui_bar_item_custom *
gui_bar_item_custom_new (const char *name, const char *conditions,
                         const char *content)
{
    struct t_config_option *option_conditions, *option_content;
    struct t_gui_bar_item_custom *new_bar_item_custom;

    if (!gui_bar_item_custom_name_valid (name))
        return NULL;

    if (gui_bar_item_custom_search (name))
        return NULL;

    if (gui_bar_item_search_default (name) >= 0)
        return NULL;

    option_conditions = NULL;
    option_content = NULL;
    new_bar_item_custom = NULL;

    option_conditions = gui_bar_item_custom_create_option (
        name,
        GUI_BAR_ITEM_CUSTOM_OPTION_CONDITIONS,
        conditions);
    if (!option_conditions)
        goto error;

    option_content = gui_bar_item_custom_create_option (
        name,
        GUI_BAR_ITEM_CUSTOM_OPTION_CONTENT,
        content);
    if (!option_content)
        goto error;

    new_bar_item_custom = gui_bar_item_custom_new_with_options (
        name,
        option_conditions,
        option_content);
    if (!new_bar_item_custom)
        goto error;

    gui_bar_item_custom_create_bar_item (new_bar_item_custom);
    if (!new_bar_item_custom->bar_item)
        goto error;

    gui_bar_item_update (name);

    return new_bar_item_custom;

error:
    if (new_bar_item_custom)
    {
        gui_bar_item_custom_free (new_bar_item_custom);
    }
    else
    {
        config_file_option_free (option_conditions, 0);
        config_file_option_free (option_content, 0);
    }
    return NULL;
}

/*
 * Use temporary custom bar items (created by reading configuration file).
 */

void
gui_bar_item_custom_use_temp_items (void)
{
    struct t_gui_bar_item_custom *ptr_temp_item, *ptr_next_temp_item;
    int i;

    ptr_temp_item = gui_temp_custom_bar_items;
    while (ptr_temp_item)
    {
        ptr_next_temp_item = ptr_temp_item->next_item;
        for (i = 0; i < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS; i++)
        {
            if (!ptr_temp_item->options[i])
            {
                ptr_temp_item->options[i] = gui_bar_item_custom_create_option (
                    ptr_temp_item->name,
                    i,
                    gui_bar_item_custom_option_default[i]);
            }
        }
        gui_bar_item_custom_create_bar_item (ptr_temp_item);
        if (!ptr_temp_item->bar_item)
        {
            if (ptr_temp_item->prev_item)
                (ptr_temp_item->prev_item)->next_item = ptr_temp_item->next_item;
            if (ptr_temp_item->next_item)
                (ptr_temp_item->next_item)->prev_item = ptr_temp_item->prev_item;
            if (gui_temp_custom_bar_items == ptr_temp_item)
                gui_temp_custom_bar_items = ptr_temp_item->next_item;
            if (last_gui_temp_custom_bar_item == ptr_temp_item)
                last_gui_temp_custom_bar_item = ptr_temp_item->prev_item;
            gui_bar_item_custom_free_data (ptr_temp_item);
            free (ptr_temp_item);
        }
        ptr_temp_item = ptr_next_temp_item;
    }

    /* remove any existing custom bar item */
    gui_bar_item_custom_free_all ();

    /* replace custom bar items list by the temporary list */
    gui_custom_bar_items = gui_temp_custom_bar_items;
    last_gui_custom_bar_item = last_gui_temp_custom_bar_item;

    gui_temp_custom_bar_items = NULL;
    last_gui_temp_custom_bar_item = NULL;
}

/*
 * Rename a custom bar item.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
gui_bar_item_custom_rename (struct t_gui_bar_item_custom *item,
                            const char *new_name)
{
    char *old_name, *option_name;
    int i, length;

    if (!item || !gui_bar_item_custom_name_valid (new_name))
        return 0;

    if (gui_bar_item_custom_search (new_name))
        return 0;

    if (gui_bar_item_search_default (new_name) >= 0)
        return 0;

    old_name = strdup (item->name);
    if (!old_name)
        return 0;

    length = strlen (new_name) + 128;
    option_name = malloc (length);
    if (!option_name)
    {
        free (old_name);
        return 0;
    }

    free (item->bar_item->name);
    item->bar_item->name = strdup (new_name);

    free (item->name);
    item->name = strdup (new_name);

    for (i = 0; i < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS; i++)
    {
        snprintf (option_name, length,
                  "%s.%s",
                  new_name,
                  gui_bar_item_custom_option_string[i]);
        config_file_option_rename (item->options[i], option_name);
    }

    gui_bar_item_update (old_name);
    gui_bar_item_update (item->name);

    free (old_name);
    free (option_name);

    return 1;
}

/*
 * Free data in a custom bar item.
 */

void
gui_bar_item_custom_free_data (struct t_gui_bar_item_custom *item)
{
    int i;

    free (item->name);
    for (i = 0; i < GUI_BAR_ITEM_CUSTOM_NUM_OPTIONS; i++)
    {
        config_file_option_free (item->options[i], 1);
    }
}

/*
 * Delete a custom bar item.
 */

void
gui_bar_item_custom_free (struct t_gui_bar_item_custom *item)
{
    char *name;

    if (!item)
        return;

    name = strdup (item->name);

    /* remove bar item */
    gui_bar_item_free (item->bar_item);

    /* remove custom bar item from custom bar items list */
    if (item->prev_item)
        (item->prev_item)->next_item = item->next_item;
    if (item->next_item)
        (item->next_item)->prev_item = item->prev_item;
    if (gui_custom_bar_items == item)
        gui_custom_bar_items = item->next_item;
    if (last_gui_custom_bar_item == item)
        last_gui_custom_bar_item = item->prev_item;

    /* free data */
    gui_bar_item_custom_free_data (item);

    free (item);

    gui_bar_item_update (name);

    free (name);
}

/*
 * Delete all custom bar items.
 */

void
gui_bar_item_custom_free_all (void)
{
    while (gui_custom_bar_items)
    {
        gui_bar_item_custom_free (gui_custom_bar_items);
    }
}
