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

/* Manage options displayed by Fast Set buffer */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#include "../weechat-plugin.h"
#include "fset.h"
#include "fset-option.h"
#include "fset-buffer.h"
#include "fset-config.h"


/* Options */
struct t_arraylist *fset_options = NULL;
int fset_option_count_marked = 0;
struct t_fset_option_max_length *fset_option_max_length = NULL;

/* Filters */
char *fset_option_filter = NULL;
struct t_hashtable *fset_option_filter_hashtable_pointers = NULL;
struct t_hashtable *fset_option_filter_hashtable_extra_vars = NULL;
struct t_hashtable *fset_option_filter_hashtable_options = NULL;

/* Refresh */
struct t_hashtable *fset_option_timer_options_changed = NULL;
struct t_hook *fset_option_timer_hook = NULL;

/* Types */
char *fset_option_type_string[FSET_OPTION_NUM_TYPES] =
{ N_("boolean"), N_("integer"), N_("string"), N_("color"), N_("enum") };
char *fset_option_type_string_short[FSET_OPTION_NUM_TYPES] =
{ "bool", "int", "str", "col", "enum" };
char *fset_option_type_string_tiny[FSET_OPTION_NUM_TYPES] =
{ "b", "i", "s", "c", "e" };


/*
 * Check if a fset option pointer is valid.
 *
 * Return:
 *   1: option exists
 *   0: option does not exist
 */

int
fset_option_valid (struct t_fset_option *fset_option)
{
    struct t_fset_option *ptr_fset_option;
    int num_options, i;

    if (!fset_option)
        return 0;

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option == fset_option)
            return 1;
    }

    /* Fset option not found */
    return 0;
}

/*
 * Search for an option by name.
 *
 * If line is not NULL, *line is set with the line number of option found
 * (-1 if line is not found).
 *
 * Return pointer to option found, NULL if not found.
 */

struct t_fset_option *
fset_option_search_by_name (const char *name, int *line)
{
    struct t_fset_option *ptr_fset_option;
    int num_options, i;

    if (line)
        *line = -1;

    if (!name)
        return NULL;

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option && (strcmp (ptr_fset_option->name, name) == 0))
        {
            if (line)
                *line = i;
            return ptr_fset_option;
        }
    }

    /* Fset option not found */
    return NULL;
}

/*
 * Check if the option value is changed (different from the default value).
 *
 * Return:
 *   1: value has been changed
 *   0: value is the same as default value
 */

int
fset_option_value_is_changed (struct t_fset_option *fset_option)
{
    if (!fset_option->value && !fset_option->default_value)
        return 0;

    if ((fset_option->value && !fset_option->default_value)
        || (!fset_option->value && fset_option->default_value))
    {
        return 1;
    }

    return (strcmp (fset_option->value,
                    fset_option->default_value) != 0) ? 1 : 0;
}

/*
 * Set the value in option, according to its type.
 */

void
fset_option_set_value_string (struct t_config_option *option,
                              enum t_fset_option_type type, void *value,
                              int default_value,
                              char **value_string)
{
    char str_value[64];
    void *ptr_string_values;

    if (!value)
    {
        *value_string = NULL;
    }
    else
    {
        switch (type)
        {
            case FSET_OPTION_TYPE_BOOLEAN:
                *value_string = strdup (*((int *)value) ? "on" : "off");
                break;
            case FSET_OPTION_TYPE_INTEGER:
                snprintf (str_value, sizeof (str_value), "%d", *((int *)value));
                *value_string = strdup (str_value);
                break;
            case FSET_OPTION_TYPE_STRING:
                *value_string = strdup (
                    (default_value) ? weechat_config_string_default (option) : weechat_config_string (option));
                break;
            case FSET_OPTION_TYPE_COLOR:
                *value_string = strdup (
                    (default_value) ? weechat_config_color_default (option) : weechat_config_color (option));
                break;
            case FSET_OPTION_TYPE_ENUM:
                ptr_string_values = weechat_config_option_get_pointer (
                    option, "string_values");
                if (ptr_string_values)
                {
                    *value_string = strdup (
                        (default_value) ? weechat_config_string_default (option) : weechat_config_string (option));
                }
                else
                {
                    *value_string = strdup ("");
                }
                break;
            case FSET_OPTION_NUM_TYPES:
                break;
        }
    }
}

/*
 * Check if a string matches a mask.
 *
 * If mask has no "*" inside, just check if "mask" is inside the "string".
 * If mask has at least one "*" inside, use the function weechat_string_match.
 *
 * Return:
 *   1: string matches mask
 *   0: string does not match mask
 */

int
fset_option_string_match (const char *string, const char *mask)
{
    if (strchr (mask, '*'))
        return weechat_string_match (string, mask, 0);
    else
        return (weechat_strcasestr (string, mask)) ? 1 : 0;
}

/*
 * Add the properties of an fset option in a hashtable
 * (keys and values must be strings).
 */

void
fset_option_add_option_in_hashtable (struct t_hashtable *hashtable,
                                     struct t_fset_option *fset_option)
{
    char *value;

    weechat_hashtable_set (hashtable, "file", fset_option->file);
    weechat_hashtable_set (hashtable, "section", fset_option->section);
    weechat_hashtable_set (hashtable, "option", fset_option->option);
    weechat_hashtable_set (hashtable, "name", fset_option->name);
    weechat_hashtable_set (hashtable, "parent_name", fset_option->parent_name);
    weechat_hashtable_set (hashtable,
                           "type", _(fset_option_type_string[fset_option->type]));
    weechat_hashtable_set (hashtable,
                           "type_en", fset_option_type_string[fset_option->type]);
    weechat_hashtable_set (hashtable,
                           "type_short", fset_option_type_string_short[fset_option->type]);
    weechat_hashtable_set (hashtable,
                           "type_tiny", fset_option_type_string_tiny[fset_option->type]);
    weechat_hashtable_set (hashtable,
                           "default_value", fset_option->default_value);
    weechat_hashtable_set (hashtable, "value", fset_option->value);
    if (fset_option->value && (fset_option->type == FSET_OPTION_TYPE_STRING))
    {
        weechat_asprintf (&value, "\"%s\"", fset_option->value);
        weechat_hashtable_set (hashtable, "quoted_value",
                               (value) ? value : fset_option->value);
        free (value);
    }
    else
    {
        weechat_hashtable_set (hashtable, "quoted_value", fset_option->value);
    }
    weechat_hashtable_set (hashtable,
                           "parent_value", fset_option->parent_value);
    weechat_hashtable_set (hashtable, "min", fset_option->min);
    weechat_hashtable_set (hashtable, "max", fset_option->max);
    weechat_hashtable_set (hashtable,
                           "description",
                           (fset_option->description && fset_option->description[0]) ?
                           _(fset_option->description) : "");
    weechat_hashtable_set (hashtable,
                           "description2",
                           (fset_option->description && fset_option->description[0]) ?
                           _(fset_option->description) : _("(no description)"));
    weechat_hashtable_set (hashtable,
                           "description_en", fset_option->description);
    weechat_hashtable_set (hashtable,
                           "description_en2",
                           (fset_option->description && fset_option->description[0]) ?
                           fset_option->description : "(no description)");
    weechat_hashtable_set (hashtable,
                           "string_values", fset_option->string_values);
    weechat_hashtable_set (hashtable,
                           "allowed_values", fset_option->allowed_values);
    weechat_hashtable_set (hashtable,
                           "default_value_undef",
                           (fset_option->default_value == NULL) ? "1" : "0");
    weechat_hashtable_set (hashtable,
                           "value_undef",
                           (fset_option->value == NULL) ? "1" : "0");
    weechat_hashtable_set (hashtable,
                           "value_changed",
                           (fset_option_value_is_changed (fset_option)) ? "1" : "0");
}

/*
 * Check if an option is matching current filter(s).
 *
 * Return:
 *   1: option is matching filter(s)
 *   0: option does not match filter(s)
 */

int
fset_option_match_filter (struct t_fset_option *fset_option, const char *filter)
{
    int match;
    char *result;

    if (!filter || !filter[0])
        return 1;

    if (strncmp (filter, "c:", 2) == 0)
    {
        /* Filter by evaluated condition. */
        weechat_hashtable_set (fset_option_filter_hashtable_pointers,
                               "fset_option", fset_option);
        fset_option_add_option_in_hashtable (
            fset_option_filter_hashtable_extra_vars,
            fset_option);
        result = weechat_string_eval_expression (
            filter + 2,
            fset_option_filter_hashtable_pointers,
            fset_option_filter_hashtable_extra_vars,
            fset_option_filter_hashtable_options);
        match = (result && (strcmp (result, "1") == 0)) ? 1 : 0;
        free (result);
        return match;
    }
    else if (strncmp (filter, "f:", 2) == 0)
    {
        /* Filter by config name. */
        return (weechat_strcasecmp (fset_option->file,
                                    filter + 2) == 0) ? 1 : 0;
    }
    else if (strncmp (filter, "t:", 2) == 0)
    {
        /* Virtual cross-type value: match themable flag (any option type). */
        if (weechat_strcasecmp (filter + 2, "themable") == 0)
            return (fset_option->themable) ? 1 : 0;

        /* Filter by type. */
        return (
            (weechat_strcasecmp (
                fset_option_type_string_short[fset_option->type],
                filter + 2) == 0)
            || (weechat_strcasecmp (
                    fset_option_type_string[fset_option->type],
                    filter + 2) == 0)) ?
            1 : 0;
    }
    else if (strncmp (filter, "d==", 3) == 0)
    {
        /* Filter by modified values (on exact value). */
        if (!fset_option_value_is_changed (fset_option))
            return 0;
        return (weechat_strcasecmp (
                    (fset_option->value) ? fset_option->value : FSET_OPTION_VALUE_NULL,
                    filter + 3) == 0) ? 1 : 0;
    }
    else if (strncmp (filter, "d=", 2) == 0)
    {
        /* Filter by modified values (on value). */
        if (!fset_option_value_is_changed (fset_option))
            return 0;
        return (fset_option_string_match (
                    (fset_option->value) ? fset_option->value : FSET_OPTION_VALUE_NULL,
                    filter + 2)) ? 1 : 0;
    }
    else if (strncmp (filter, "d:", 2) == 0)
    {
        /* Filter by modified values (on name). */
        if (!fset_option_value_is_changed (fset_option))
            return 0;
        return fset_option_string_match (fset_option->name,
                                         filter + 2) ? 1 : 0;
    }
    else if (strcmp (filter, "d") == 0)
    {
        /* Filter by modified values. */
        return (fset_option_value_is_changed (fset_option)) ? 1 : 0;
    }
    else if (strncmp (filter, "h=", 2) == 0)
    {
        /* Filter by help text (translated). */
        return (fset_option_string_match (
                    (fset_option->description && fset_option->description[0]) ?
                    _(fset_option->description) : "",
                    filter + 2)) ? 1 : 0;
    }
    else if (strncmp (filter, "he=", 3) == 0)
    {
        /* Filter by help text (in English). */
        return (fset_option_string_match (
                    (fset_option->description && fset_option->description[0]) ?
                    fset_option->description : "",
                    filter + 3)) ? 1 : 0;
    }
    else if (strncmp (filter, "==", 2) == 0)
    {
        /* Filter by exact value. */
        return (weechat_strcasecmp (
                    (fset_option->value) ? fset_option->value : FSET_OPTION_VALUE_NULL,
                    filter + 2) == 0) ? 1 : 0;
    }
    else if (filter[0] == '=')
    {
        /* Filter by value. */
        return (fset_option_string_match (
                    (fset_option->value) ? fset_option->value : FSET_OPTION_VALUE_NULL,
                    filter + 1)) ? 1 : 0;
    }
    else
    {
        /* Filter by option name. */
        return (fset_option_string_match (fset_option->name, filter)) ? 1 : 0;
    }
}

/*
 * Set (or sets again) values (except name) in an fset option.
 */

void
fset_option_set_values (struct t_fset_option *fset_option,
                        struct t_config_option *option)
{
    const char *ptr_config_name, *ptr_section_name, *ptr_option_name;
    const char *ptr_parent_name, *ptr_description;
    const char **ptr_string_values;
    void *ptr_default_value, *ptr_value;
    struct t_config_option *ptr_parent_option;
    int *ptr_type, *ptr_themable, *ptr_min, *ptr_max;
    char str_value[64], str_allowed_values[4096];

    /* file */
    free (fset_option->file);
    fset_option->file = NULL;
    ptr_config_name = weechat_config_option_get_string (option, "config_name");
    fset_option->file = strdup (ptr_config_name);

    /* section */
    free (fset_option->section);
    fset_option->section = NULL;
    ptr_section_name = weechat_config_option_get_string (option, "section_name");
    fset_option->section = strdup (ptr_section_name);

    /* option */
    free (fset_option->option);
    fset_option->option = NULL;
    ptr_option_name = weechat_config_option_get_string (option, "name");
    fset_option->option = strdup (ptr_option_name);

    /* name */
    free (fset_option->name);
    fset_option->name = NULL;
    weechat_asprintf (&fset_option->name,
                      "%s.%s.%s",
                      ptr_config_name,
                      ptr_section_name,
                      ptr_option_name);

    /* parent name */
    free (fset_option->parent_name);
    fset_option->parent_name = NULL;
    ptr_parent_name = weechat_config_option_get_string (option, "parent_name");
    fset_option->parent_name = (ptr_parent_name) ? strdup (ptr_parent_name) : NULL;

    /* type */
    ptr_type = weechat_config_option_get_pointer (option, "type");
    fset_option->type = *ptr_type;

    /* themable */
    ptr_themable = weechat_config_option_get_pointer (option, "themable");
    fset_option->themable = (ptr_themable) ? *ptr_themable : 0;

    /* default value */
    free (fset_option->default_value);
    fset_option->default_value = NULL;
    ptr_default_value = weechat_config_option_get_pointer (option,
                                                           "default_value");
    fset_option_set_value_string (option,
                                  fset_option->type,
                                  ptr_default_value,
                                  1,
                                  &fset_option->default_value);

    /* value */
    free (fset_option->value);
    fset_option->value = NULL;
    ptr_value = weechat_config_option_get_pointer (option, "value");
    fset_option_set_value_string (option,
                                  fset_option->type,
                                  ptr_value,
                                  0,
                                  &fset_option->value);

    /* parent_value */
    free (fset_option->parent_value);
    fset_option->parent_value = NULL;
    if (ptr_parent_name)
    {
        ptr_parent_option = weechat_config_get (ptr_parent_name);
        if (ptr_parent_option)
        {
            ptr_value = weechat_config_option_get_pointer (ptr_parent_option,
                                                           "value");
            fset_option_set_value_string (ptr_parent_option,
                                          fset_option->type,
                                          ptr_value,
                                          0,
                                          &fset_option->parent_value);
        }
    }

    /* min value */
    free (fset_option->min);
    fset_option->min = NULL;
    ptr_min = weechat_config_option_get_pointer (option, "min");
    snprintf (str_value, sizeof (str_value), "%d", *ptr_min);
    fset_option->min = strdup (str_value);

    /* max value */
    free (fset_option->max);
    fset_option->max = NULL;
    ptr_max = weechat_config_option_get_pointer (option, "max");
    snprintf (str_value, sizeof (str_value), "%d", *ptr_max);
    fset_option->max = strdup (str_value);

    /* description */
    free (fset_option->description);
    fset_option->description = NULL;
    ptr_description = weechat_config_option_get_string (option, "description");
    fset_option->description = strdup ((ptr_description) ? ptr_description : "");

    /* string_values */
    free (fset_option->string_values);
    fset_option->string_values = NULL;
    ptr_string_values = weechat_config_option_get_pointer (option, "string_values");
    if (ptr_string_values)
    {
        fset_option->string_values = weechat_string_rebuild_split_string (
            ptr_string_values, ",", 0, -1);
    }
    else
    {
        fset_option->string_values = strdup ("");
    }

    /* allowed_values */
    free (fset_option->allowed_values);
    fset_option->allowed_values = NULL;
    str_allowed_values[0] = '\0';
    switch (fset_option->type)
    {
        case FSET_OPTION_TYPE_BOOLEAN:
            snprintf (str_allowed_values, sizeof (str_allowed_values),
                      "on,off");
            break;
        case FSET_OPTION_TYPE_INTEGER:
            snprintf (str_allowed_values, sizeof (str_allowed_values),
                      "%d..%d", *ptr_min, *ptr_max);
            break;
        case FSET_OPTION_TYPE_STRING:
            snprintf (str_allowed_values, sizeof (str_allowed_values),
                      "%s", _("any string"));
            break;
        case FSET_OPTION_TYPE_COLOR:
            snprintf (str_allowed_values, sizeof (str_allowed_values),
                      "%s", _("any color"));
            break;
        case FSET_OPTION_TYPE_ENUM:
            snprintf (str_allowed_values, sizeof (str_allowed_values),
                      "%s", fset_option->string_values);
            break;
        case FSET_OPTION_NUM_TYPES:
            break;
    }
    fset_option->allowed_values = strdup (str_allowed_values);
}

/*
 * Set max length for fields, for one option.
 */

void
fset_option_set_max_length_fields_option (struct t_fset_option *fset_option)
{
    int length, length_value, length_parent_value;

    /* file */
    length = weechat_utf8_strlen_screen (fset_option->file);
    if (length > fset_option_max_length->file)
        fset_option_max_length->file = length;

    /* section */
    length = weechat_utf8_strlen_screen (fset_option->section);
    if (length > fset_option_max_length->section)
        fset_option_max_length->section = length;

    /* option */
    length = weechat_utf8_strlen_screen (fset_option->option);
    if (length > fset_option_max_length->option)
        fset_option_max_length->option = length;

    /* name */
    length = weechat_utf8_strlen_screen (fset_option->name);
    if (length > fset_option_max_length->name)
        fset_option_max_length->name = length;

    /* parent_name */
    length = (fset_option->parent_name) ?
        weechat_utf8_strlen_screen (fset_option->name) : 0;
    if (length > fset_option_max_length->parent_name)
        fset_option_max_length->parent_name = length;

    /* type */
    length = weechat_utf8_strlen_screen (_(fset_option_type_string[fset_option->type]));
    if (length > fset_option_max_length->type)
        fset_option_max_length->type = length;

    /* type_en */
    length = weechat_utf8_strlen_screen (fset_option_type_string[fset_option->type]);
    if (length > fset_option_max_length->type_en)
        fset_option_max_length->type_en = length;

    /* type_short */
    length = weechat_utf8_strlen_screen (fset_option_type_string_short[fset_option->type]);
    if (length > fset_option_max_length->type_short)
        fset_option_max_length->type_short = length;

    /* type_tiny */
    length = weechat_utf8_strlen_screen (fset_option_type_string_tiny[fset_option->type]);
    if (length > fset_option_max_length->type_tiny)
        fset_option_max_length->type_tiny = length;

    /* default_value */
    if (fset_option->default_value)
    {
        length = weechat_utf8_strlen_screen (fset_option->default_value);
        if (fset_option->type == FSET_OPTION_TYPE_STRING)
            length += 2;
    }
    else
    {
        length = weechat_utf8_strlen_screen (FSET_OPTION_VALUE_NULL);
    }
    if (length > fset_option_max_length->default_value)
        fset_option_max_length->default_value = length;

    /* value */
    if (fset_option->value)
    {
        length_value = weechat_utf8_strlen_screen (fset_option->value);
        if (fset_option->type == FSET_OPTION_TYPE_STRING)
            length_value += 2;
    }
    else
    {
        length_value = weechat_utf8_strlen_screen (FSET_OPTION_VALUE_NULL);
    }
    if (length_value > fset_option_max_length->value)
        fset_option_max_length->value = length_value;

    /* parent_value */
    if (fset_option->parent_value)
    {
        length_parent_value = weechat_utf8_strlen_screen (fset_option->parent_value);
        if (fset_option->type == FSET_OPTION_TYPE_STRING)
            length_parent_value += 2;
    }
    else
    {
        length_parent_value = weechat_utf8_strlen_screen (FSET_OPTION_VALUE_NULL);
    }
    if (length_parent_value > fset_option_max_length->parent_value)
        fset_option_max_length->parent_value = length_parent_value;

    /* value2 */
    length = length_value;
    if (!fset_option->value)
        length += 4 + length_parent_value;
    if (length > fset_option_max_length->value2)
        fset_option_max_length->value2 = length;

    /* min */
    length = weechat_utf8_strlen_screen (fset_option->min);
    if (length > fset_option_max_length->min)
        fset_option_max_length->min = length;

    /* max */
    length = weechat_utf8_strlen_screen (fset_option->max);
    if (length > fset_option_max_length->max)
        fset_option_max_length->max = length;

    /* description */
    length = (fset_option->description && fset_option->description[0]) ?
        weechat_utf8_strlen_screen (_(fset_option->description)) : 0;
    if (length > fset_option_max_length->description)
        fset_option_max_length->description = length;

    /* description2 */
    length = weechat_utf8_strlen_screen (
        (fset_option->description && fset_option->description[0]) ?
        _(fset_option->description) : _("(no description)"));
    if (length > fset_option_max_length->description2)
        fset_option_max_length->description2 = length;

    /* description_en */
    length = weechat_utf8_strlen_screen (fset_option->description);
    if (length > fset_option_max_length->description_en)
        fset_option_max_length->description_en = length;

    /* description_en2 */
    length = weechat_utf8_strlen_screen (
        (fset_option->description && fset_option->description[0]) ?
        fset_option->description : _("(no description)"));
    if (length > fset_option_max_length->description_en2)
        fset_option_max_length->description_en2 = length;

    /* string_values */
    length = weechat_utf8_strlen_screen (fset_option->string_values);
    if (length > fset_option_max_length->string_values)
        fset_option_max_length->string_values = length;

    /* allowed_values */
    length = weechat_utf8_strlen_screen (fset_option->allowed_values);
    if (length > fset_option_max_length->allowed_values)
        fset_option_max_length->allowed_values = length;

    /* marked */
    length = weechat_utf8_strlen_screen (weechat_config_string (fset_config_look_marked_string));
    if (length > fset_option_max_length->marked)
        fset_option_max_length->marked = length;
    length = weechat_utf8_strlen_screen (weechat_config_string (fset_config_look_unmarked_string));
    if (length > fset_option_max_length->marked)
        fset_option_max_length->marked = length;
}

/*
 * Initialize max length for fields.
 */

void
fset_option_init_max_length (struct t_fset_option_max_length *max_length)
{
    memset (max_length, 0, sizeof (*max_length));
}

/*
 * Set max length for fields, for all options.
 */

void
fset_option_set_max_length_fields_all (void)
{
    int i, num_options;
    struct t_fset_option *ptr_fset_option;

    /* First clear all max lengths. */
    fset_option_init_max_length (fset_option_max_length);

    /* Set max length for fields, for all options. */
    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option)
            fset_option_set_max_length_fields_option (ptr_fset_option);
    }
}

/*
 * Allocate an fset option structure using a pointer to a WeeChat/plugin option.
 *
 * Return pointer to new fset option, NULL if error.
 */

struct t_fset_option *
fset_option_alloc (struct t_config_option *option)
{
    struct t_fset_option *new_fset_option;

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

    new_fset_option->index = 0;
    new_fset_option->file = NULL;
    new_fset_option->section = NULL;
    new_fset_option->option = NULL;
    new_fset_option->name = NULL;
    new_fset_option->parent_name = NULL;
    new_fset_option->type = 0;
    new_fset_option->themable = 0;
    new_fset_option->default_value = NULL;
    new_fset_option->value = NULL;
    new_fset_option->parent_value = NULL;
    new_fset_option->min = NULL;
    new_fset_option->max = NULL;
    new_fset_option->description = NULL;
    new_fset_option->string_values = NULL;
    new_fset_option->allowed_values = NULL;
    new_fset_option->marked = 0;

    fset_option_set_values (new_fset_option, option);

    return new_fset_option;
}

/*
 * Allocate an fset option structure using a pointer to a WeeChat/plugin option.
 *
 * Return pointer to new fset option, NULL if the option does not match
 * filters or if error.
 */

struct t_fset_option *
fset_option_add (struct t_config_option *option)
{
    struct t_fset_option *new_fset_option;

    new_fset_option = fset_option_alloc (option);
    if (!new_fset_option)
        return NULL;

    if (!weechat_config_boolean (fset_config_look_show_plugins_desc)
        && (strcmp (new_fset_option->file, "plugins") == 0)
        && (strcmp (new_fset_option->section, "desc") == 0))
    {
        fset_option_free (new_fset_option);
        return NULL;
    }

    /* Check if option match filters (if not, ignore it). */
    if (!fset_option_match_filter (new_fset_option, fset_option_filter))
    {
        fset_option_free (new_fset_option);
        return NULL;
    }

    fset_option_set_max_length_fields_option (new_fset_option);

    return new_fset_option;
}

/*
 * Compare two options to sort them by name.
 */

int
fset_option_compare_options_cb (void *data, struct t_arraylist *arraylist,
                                void *pointer1, void *pointer2)
{
    int i, reverse, case_sensitive, rc;
    const char *ptr_field;

    /* Make C compiler happy. */
    (void) data;
    (void) arraylist;

    if (!fset_hdata_fset_option)
        return 1;

    for (i = 0; i < fset_config_sort_fields_count; i++)
    {
        reverse = 1;
        case_sensitive = 1;
        ptr_field = fset_config_sort_fields[i];
        while ((ptr_field[0] == '-') || (ptr_field[0] == '~'))
        {
            if (ptr_field[0] == '-')
                reverse *= -1;
            else if (ptr_field[0] == '~')
                case_sensitive ^= 1;
            ptr_field++;
        }
        rc = weechat_hdata_compare (fset_hdata_fset_option,
                                    pointer1, pointer2,
                                    ptr_field,
                                    case_sensitive);
        rc *= reverse;
        if (rc != 0)
            return rc;
    }

    return 1;
}

/*
 * Free an fset option.
 */

void
fset_option_free (struct t_fset_option *fset_option)
{
    if (!fset_option)
        return;

    free (fset_option->file);
    free (fset_option->section);
    free (fset_option->option);
    free (fset_option->name);
    free (fset_option->parent_name);
    free (fset_option->default_value);
    free (fset_option->value);
    free (fset_option->parent_value);
    free (fset_option->min);
    free (fset_option->max);
    free (fset_option->description);
    free (fset_option->string_values);
    free (fset_option->allowed_values);

    free (fset_option);
}

/*
 * Free an fset option (arraylist callback).
 */

void
fset_option_free_cb (void *data, struct t_arraylist *arraylist, void *pointer)
{
    struct t_fset_option *fset_option;

    /* Make C compiler happy. */
    (void) data;
    (void) arraylist;

    fset_option = (struct t_fset_option *)pointer;

    fset_option_free (fset_option);
}

/*
 * Allocate and return the arraylist to store options.
 */

struct t_arraylist *
fset_option_get_arraylist_options (void)
{
    return weechat_arraylist_new (100, 1, 0,
                                  &fset_option_compare_options_cb, NULL,
                                  &fset_option_free_cb, NULL);
}

/*
 * Allocate and return the structure to store max length of fields.
 */

struct t_fset_option_max_length *
fset_option_get_max_length (void)
{
    struct t_fset_option_max_length *max_length;

    max_length = malloc (sizeof (*fset_option_max_length));
    if (max_length)
        fset_option_init_max_length (max_length);

    return max_length;
}

/*
 * Get all options to display in fset buffer.
 */

void
fset_option_get_options (void)
{
    struct t_fset_option *new_fset_option, *ptr_fset_option;
    struct t_config_file *ptr_config;
    struct t_config_section *ptr_section;
    struct t_config_option *ptr_option;
    struct t_hashtable *marked_options;
    int i, num_options;

    /* Save marked options in a hashtable. */
    if (!weechat_config_boolean (fset_config_look_auto_unmark))
    {
        marked_options = weechat_hashtable_new (256,
                                                WEECHAT_HASHTABLE_STRING,
                                                WEECHAT_HASHTABLE_POINTER,
                                                NULL, NULL);
        num_options = weechat_arraylist_size (fset_options);
        for (i = 0; i < num_options; i++)
        {
            ptr_fset_option = weechat_arraylist_get (fset_options, i);
            if (ptr_fset_option && ptr_fset_option->marked)
                weechat_hashtable_set (marked_options, ptr_fset_option->name, NULL);
        }
    }
    else
    {
        marked_options = NULL;
    }

    /* Clear options. */
    weechat_arraylist_clear (fset_options);
    fset_option_count_marked = 0;
    fset_option_init_max_length (fset_option_max_length);

    /* Get options. */
    ptr_config = weechat_hdata_get_list (fset_hdata_config_file,
                                         "config_files");
    while (ptr_config)
    {
        ptr_section = weechat_hdata_pointer (fset_hdata_config_file,
                                             ptr_config, "sections");
        while (ptr_section)
        {
            ptr_option = weechat_hdata_pointer (fset_hdata_config_section,
                                                ptr_section, "options");
            while (ptr_option)
            {
                new_fset_option = fset_option_add (ptr_option);
                if (new_fset_option)
                    weechat_arraylist_add (fset_options, new_fset_option);
                ptr_option = weechat_hdata_move (fset_hdata_config_option,
                                                 ptr_option, 1);
            }
            ptr_section = weechat_hdata_move (fset_hdata_config_section,
                                              ptr_section, 1);
        }
        ptr_config = weechat_hdata_move (fset_hdata_config_file,
                                         ptr_config, 1);
    }

    num_options = weechat_arraylist_size (fset_options);

    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option)
            ptr_fset_option->index = i;
    }

    /* Check selected line. */
    if (num_options == 0)
        fset_buffer_selected_line = 0;
    else if (fset_buffer_selected_line >= num_options)
        fset_buffer_selected_line = num_options - 1;

    /* Restore marked options. */
    if (marked_options)
    {
        for (i = 0; i < num_options; i++)
        {
            ptr_fset_option = weechat_arraylist_get (fset_options, i);
            if (ptr_fset_option
                && weechat_hashtable_has_key (marked_options,
                                              ptr_fset_option->name))
            {
                ptr_fset_option->marked = 1;
                fset_option_count_marked++;
            }
        }
        weechat_hashtable_free (marked_options);
    }
}

/*
 * Set the filter.
 */

void
fset_option_set_filter (const char *filter)
{
    free (fset_option_filter);
    fset_option_filter = (filter && (strcmp (filter, "*") != 0)) ?
        strdup (filter) : NULL;
}

/*
 * Filter options.
 */

void
fset_option_filter_options (const char *filter)
{
    fset_buffer_selected_line = 0;

    fset_option_set_filter (filter);

    fset_buffer_set_localvar_filter ();

    fset_option_get_options ();

    fset_buffer_refresh (1);
}

/*
 * Toggle a boolean option.
 */

void
fset_option_toggle_value (struct t_fset_option *fset_option,
                          struct t_config_option *option)
{
    if (!fset_option || !option
        || (fset_option->type != FSET_OPTION_TYPE_BOOLEAN))
        return;

    weechat_config_option_set (option, "toggle", 1);
}

/*
 * Add a value to an integer/enum/color option.
 */

void
fset_option_add_value (struct t_fset_option *fset_option,
                       struct t_config_option *option,
                       int value)
{
    char str_value[128];

    if (!fset_option || !option
        || ((fset_option->type != FSET_OPTION_TYPE_INTEGER)
            && (fset_option->type != FSET_OPTION_TYPE_COLOR)
            && (fset_option->type != FSET_OPTION_TYPE_ENUM)))
        return;

    snprintf (str_value, sizeof (str_value),
              "%s%d",
              (value > 0) ? "++" : "--",
              (value > 0) ? value : value * -1);
    weechat_config_option_set (option, str_value, 1);
}

/*
 * Reset the value of an option.
 */

void
fset_option_reset_value (struct t_fset_option *fset_option,
                         struct t_config_option *option)
{
    /* Make C compiler happy. */
    (void) fset_option;

    if (!option)
        return;

    weechat_config_option_reset (option, 1);
}

/*
 * Unset the value of an option.
 */

void
fset_option_unset_value (struct t_fset_option *fset_option,
                         struct t_config_option *option)
{
    /* Make C compiler happy. */
    (void) fset_option;

    if (!option)
        return;

    weechat_config_option_unset (option);
}

/*
 * Set the value of an option.
 *
 * If set_mode == -1, edit an empty value.
 * If set_mode == 0, edit the current value.
 * If set_mode == 1, append to the current value (move the cursor at the end of
 * value)
 */

void
fset_option_set (struct t_fset_option *fset_option,
                 struct t_config_option *option,
                 struct t_gui_buffer *buffer,
                 int set_mode)
{
    int use_mute, add_quotes, input_pos;
    char *ptr_value, *str_input, str_pos[32];
    char empty_value[1] = { '\0' };

    /* Make C compiler happy. */
    (void) option;

    if (!fset_option)
        return;

    if (set_mode == -1)
        ptr_value = empty_value;
    else
        ptr_value = (fset_option->value) ? fset_option->value : empty_value;

    use_mute = weechat_config_boolean (fset_config_look_use_mute);
    add_quotes = (fset_option->type == FSET_OPTION_TYPE_STRING) ? 1 : 0;
    if (weechat_asprintf (
            &str_input,
            "%s/set %s %s%s%s",
            (use_mute) ? "/mute " : "",
            fset_option->name,
            (add_quotes) ? "\"" : "",
            ptr_value,
            (add_quotes) ? "\"" : "") < 0)
    {
        return;
    }
    weechat_buffer_set (buffer, "input", str_input);
    input_pos = ((use_mute) ? 6 : 0) +  /* "/mute " */
        5 +  /* "/set " */
        weechat_utf8_strlen (fset_option->name) + 1 +
        ((add_quotes) ? 1 : 0) +
        ((set_mode == 1) ? ((fset_option->value) ?
                            weechat_utf8_strlen (fset_option->value) : 0) : 0);
    snprintf (str_pos, sizeof (str_pos), "%d", input_pos);
    weechat_buffer_set (buffer, "input_pos", str_pos);

    free (str_input);
}

/*
 * Mark/unmark an option.
 */

void
fset_option_toggle_mark (struct t_fset_option *fset_option,
                         struct t_config_option *option)
{
    /* Make C compiler happy. */
    (void) option;

    if (!fset_option)
        return;

    fset_option->marked ^= 1;
    fset_option_count_marked += (fset_option->marked) ? 1 : -1;

    (void) fset_buffer_display_option (fset_option);
}

/*
 * Mark/unmark options matching a filter.
 */

void
fset_option_mark_options_matching_filter (const char *filter, int mark)
{
    int num_options, i, mark_old, matching, set_title;
    struct t_fset_option *ptr_fset_option;

    set_title = 0;

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option)
        {
            mark_old = ptr_fset_option->marked;
            matching = fset_option_match_filter (ptr_fset_option, filter);
            if (matching)
            {
                if (!mark_old && mark)
                {
                    ptr_fset_option->marked = 1;
                    fset_option_count_marked++;
                    (void) fset_buffer_display_option (ptr_fset_option);
                    set_title = 1;
                }
                else if (mark_old && !mark)
                {
                    ptr_fset_option->marked = 0;
                    fset_option_count_marked--;
                    (void) fset_buffer_display_option (ptr_fset_option);
                    set_title = 1;
                }
            }
        }
    }

    if (set_title)
        fset_buffer_set_title ();
}

/*
 * Unmark all options.
 */

void
fset_option_unmark_all (void)
{
    int num_options, marked, set_title, i;
    struct t_fset_option *ptr_fset_option;

    set_title = 0;

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option)
        {
            marked = ptr_fset_option->marked;
            ptr_fset_option->marked = 0;
            if (marked)
            {
                (void) fset_buffer_display_option (ptr_fset_option);
                set_title = 1;
            }
        }
    }
    fset_option_count_marked = 0;

    if (set_title)
        fset_buffer_set_title ();
}

/*
 * Export options currently displayed in fset buffer.
 *
 * If with_help == 1, the help is displayed above each option
 * and options are separated by an empty line.
 *
 * Return:
 *   1: export OK
 *   0: error
 */

int
fset_option_export (const char *filename, int with_help)
{
    int num_options, i;
    char *filename2, *line;
    FILE *file;
    struct t_fset_option *ptr_fset_option;
    struct t_hashtable *hashtable_pointers, *hashtable_extra_vars;

    filename2 = weechat_string_expand_home (filename);
    if (!filename2)
        return 0;

    file = fopen (filename2, "w");
    if (!file)
    {
        free (filename2);
        return 0;
    }

    fchmod (fileno (file), 0600);

    hashtable_pointers = weechat_hashtable_new (
        8,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_POINTER,
        NULL, NULL);
    hashtable_extra_vars = weechat_hashtable_new (
        128,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (ptr_fset_option)
        {
            weechat_hashtable_set (hashtable_pointers,
                                   "fset_option", ptr_fset_option);
            fset_option_add_option_in_hashtable (hashtable_extra_vars,
                                                 ptr_fset_option);
            if (with_help)
            {
                if (i > 0)
                    fprintf (file, "\n");
                line = weechat_string_eval_expression (
                    weechat_config_string (fset_config_format_export_help),
                    hashtable_pointers,
                    hashtable_extra_vars,
                    NULL);
                if (line && line[0])
                    fprintf (file, "%s\n", line);
                free (line);
            }
            line = weechat_string_eval_expression (
                (ptr_fset_option->value) ?
                weechat_config_string (fset_config_format_export_option) :
                weechat_config_string (fset_config_format_export_option_null),
                hashtable_pointers,
                hashtable_extra_vars,
                NULL);
            if (line && line[0])
                fprintf (file, "%s\n", line);
            free (line);
        }
    }

    fclose (file);

    weechat_hashtable_free (hashtable_pointers);
    weechat_hashtable_free (hashtable_extra_vars);
    free (filename2);

    return 1;
}

/*
 * Import options from a file: all lines starting with "/" are executed.
 *
 * Return:
 *     -2: not enough memory
 *     -1: file not found
 *   >= 0: number of commands executed in file
 */

int
fset_option_import (const char *filename)
{
    char *filename2, line[4096], *ptr_line;
    FILE *file;
    int length, count;

    count = 0;

    filename2 = weechat_string_expand_home (filename);
    if (!filename2)
        return -2;

    file = fopen (filename2, "r");
    if (!file)
    {
        free (filename2);
        return -1;
    }

    while (!feof (file))
    {
        ptr_line = fgets (line, sizeof (line) - 1, file);
        if (!ptr_line)
            continue;
        /* Ignore comments. */
        if (ptr_line[0] == '#')
            continue;
        /* Execute command (if it's a valid command). */
        if (!weechat_string_input_for_buffer (ptr_line))
        {
            /* Remove trailing '\r' and '\n'. */
            length = strlen (line) - 1;
            while ((length >= 0)
                   && ((line[length] == '\n')
                       || (line[length] == '\r')))
            {
                line[length] = '\0';
                length--;
            }
            if (ptr_line[0])
            {
                weechat_command (NULL, ptr_line);
                count++;
            }
        }
    }

    fclose (file);

    free (filename2);

    return count;
}

/*
 * Refresh the fset buffer after the change of an option.
 */

void
fset_option_config_changed (const char *option_name)
{
    struct t_fset_option *ptr_fset_option, *new_fset_option;
    struct t_config_option *ptr_option;
    int option_removed, option_added, line, num_options;
    char *old_name_selected;

    if (!fset_buffer)
        return;

    option_removed = 0;
    option_added = 0;

    ptr_fset_option = weechat_arraylist_get (fset_options,
                                             fset_buffer_selected_line);
    old_name_selected = (ptr_fset_option) ?
        strdup (ptr_fset_option->name) : NULL;

    ptr_fset_option = (option_name) ?
        fset_option_search_by_name (option_name, &line) : NULL;
    ptr_option = (option_name) ? weechat_config_get (option_name) : NULL;


    if (ptr_fset_option)
    {
        if (ptr_option)
        {
            fset_option_set_values (ptr_fset_option, ptr_option);
            (void) fset_buffer_display_option (ptr_fset_option);
        }
        else
        {
            /* Option removed: get options and refresh the whole buffer. */
            option_removed = 1;
            if (ptr_fset_option->index < fset_buffer_selected_line)
                fset_buffer_selected_line--;
        }
    }
    else if (ptr_option)
    {
        new_fset_option = fset_option_alloc (ptr_option);
        if (fset_option_match_filter (new_fset_option, fset_option_filter))
        {
            /* Option added: get options and refresh the whole buffer. */
            option_added = 1;
        }
        fset_option_free (new_fset_option);
    }

    if (option_removed || option_added)
    {
        fset_option_get_options ();
        /*
         * In case of option added, we move to the next one if is was the
         * selected one.
         */
        if (option_added && old_name_selected)
        {
            ptr_fset_option = weechat_arraylist_get (
                fset_options, fset_buffer_selected_line + 1);
            if (ptr_fset_option
                && (strcmp (old_name_selected, ptr_fset_option->name) == 0))
            {
                fset_buffer_selected_line++;
            }
        }
        fset_buffer_refresh (0);
        fset_buffer_check_line_outside_window ();
    }
    else
    {
        num_options = weechat_arraylist_size (fset_options);
        for (line = 0; line < num_options; line++)
        {
            ptr_fset_option = weechat_arraylist_get (fset_options, line);
            if (ptr_fset_option
                && ptr_fset_option->parent_name
                && option_name
                && (strcmp (ptr_fset_option->parent_name, option_name) == 0))
            {
                ptr_option = weechat_config_get (ptr_fset_option->name);
                if (ptr_option)
                    fset_option_set_values (ptr_fset_option, ptr_option);
            }
        }
        fset_option_set_max_length_fields_all ();
        fset_buffer_refresh (0);
    }

    free (old_name_selected);
}

/*
 * Callback called by the timer for each option changed.
 */

void
fset_option_timer_option_changed_cb (void *data,
                                     struct t_hashtable *hashtable,
                                     const void *key,
                                     const void *value)
{
    /* Make C compiler happy. */
    (void) data;
    (void) hashtable;
    (void) value;

    fset_option_config_changed (key);
}

/*
 * Callback for timer after an option is changed.
 */

int
fset_option_config_timer_cb (const void *pointer,
                             void *data,
                             int remaining_calls)
{
    /* Make C compiler happy. */
    (void) pointer;
    (void) data;
    (void) remaining_calls;

    if (weechat_hashtable_get_integer (
            fset_option_timer_options_changed,
            "items_count") >= FSET_OPTION_TIMER_MAX_OPTIONS_CHANGED)
    {
        fset_option_get_options ();
        fset_buffer_refresh (1);
    }
    else
    {
        weechat_hashtable_map (fset_option_timer_options_changed,
                               &fset_option_timer_option_changed_cb,
                               NULL);
    }

    weechat_hashtable_remove_all (fset_option_timer_options_changed);

    fset_option_timer_hook = NULL;

    return WEECHAT_RC_OK;
}

/*
 * Callback for config option changed.
 */

int
fset_option_config_cb (const void *pointer,
                       void *data,
                       const char *option,
                       const char *value)
{
    char *info;

    /* Make C compiler happy. */
    (void) pointer;
    (void) data;
    (void) value;

    /* Do nothing if fset buffer is not opened. */
    if (!fset_buffer)
        return WEECHAT_RC_OK;

    /* Do nothing if auto-refresh is disabled for this option. */
    if (!weechat_string_match_list (option,
                                    (const char **)fset_config_auto_refresh,
                                    0))
        return WEECHAT_RC_OK;

    /* Do nothing if WeeChat is upgrading. */
    info = weechat_info_get ("weechat_upgrading", NULL);
    if (info && (strcmp (info, "1") == 0))
    {
        free (info);
        return WEECHAT_RC_OK;
    }
    free (info);

    /*
     * We limit the number of options to display with the timer; for example
     * on /reload, many options are changed, so we'll get all options and
     * display them, instead of change them one by one, which is very slow.
     */
    if (weechat_hashtable_get_integer (
            fset_option_timer_options_changed,
            "items_count") < FSET_OPTION_TIMER_MAX_OPTIONS_CHANGED)
    {
        weechat_hashtable_set (fset_option_timer_options_changed,
                               option, NULL);
    }

    if (!fset_option_timer_hook)
    {
        fset_option_timer_hook = weechat_hook_timer (
            1, 0, 1,
            &fset_option_config_timer_cb, NULL, NULL);
    }

    return WEECHAT_RC_OK;
}

/*
 * Return hdata for option.
 */

struct t_hdata *
fset_option_hdata_option_cb (const void *pointer, void *data,
                             const char *hdata_name)
{
    struct t_hdata *hdata;

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

    hdata = weechat_hdata_new (hdata_name, NULL, NULL, 0, 0, NULL, NULL);
    if (hdata)
    {
        WEECHAT_HDATA_VAR(struct t_fset_option, index, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, file, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, section, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, option, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, name, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, parent_name, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, type, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, themable, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, default_value, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, value, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, parent_value, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, min, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, max, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, description, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, string_values, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_fset_option, marked, INTEGER, 0, NULL, NULL);
    }
    return hdata;
}

/*
 * Add a fset option in an infolist.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
fset_option_add_to_infolist (struct t_infolist *infolist,
                             struct t_fset_option *fset_option)
{
    struct t_infolist_item *ptr_item;

    if (!infolist || !fset_option)
        return 0;

    ptr_item = weechat_infolist_new_item (infolist);
    if (!ptr_item)
        return 0;

    if (!weechat_infolist_new_var_integer (ptr_item, "index", fset_option->index))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "file", fset_option->file))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "section", fset_option->section))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "option", fset_option->option))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "name", fset_option->name))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "parent_name", fset_option->parent_name))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "type", _(fset_option_type_string[fset_option->type])))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "type_en", fset_option_type_string[fset_option->type]))
        return 0;
    if (!weechat_infolist_new_var_integer (ptr_item, "themable", fset_option->themable))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "default_value", fset_option->default_value))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "value", fset_option->value))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "parent_value", fset_option->parent_value))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "min", fset_option->min))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "max", fset_option->max))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "description",
                                          (fset_option->description && fset_option->description[0]) ?
                                          _(fset_option->description) : ""))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "description_en", fset_option->description))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "string_values", fset_option->description))
        return 0;
    if (!weechat_infolist_new_var_string (ptr_item, "allowed_values", fset_option->allowed_values))
        return 0;
    if (!weechat_infolist_new_var_integer (ptr_item, "marked", fset_option->marked))
        return 0;

    return 1;
}

/*
 * Print fset options in WeeChat log file (usually for crash dump).
 */

void
fset_option_print_log (void)
{
    struct t_fset_option *ptr_fset_option;
    int num_options, i;

    num_options = weechat_arraylist_size (fset_options);
    for (i = 0; i < num_options; i++)
    {
        ptr_fset_option = weechat_arraylist_get (fset_options, i);
        if (!ptr_fset_option)
            continue;
        weechat_log_printf ("");
        weechat_log_printf ("[fset option (addr:%p)]", ptr_fset_option);
        weechat_log_printf ("  index . . . . . . . . : %d", ptr_fset_option->index);
        weechat_log_printf ("  file. . . . . . . . . : '%s'", ptr_fset_option->file);
        weechat_log_printf ("  section . . . . . . . : '%s'", ptr_fset_option->section);
        weechat_log_printf ("  option. . . . . . . . : '%s'", ptr_fset_option->option);
        weechat_log_printf ("  name. . . . . . . . . : '%s'", ptr_fset_option->name);
        weechat_log_printf ("  parent_name . . . . . : '%s'", ptr_fset_option->parent_name);
        weechat_log_printf ("  type. . . . . . . . . : %d ('%s')",
                            ptr_fset_option->type,
                            fset_option_type_string[ptr_fset_option->type]);
        weechat_log_printf ("  themable. . . . . . . : %d", ptr_fset_option->themable);
        weechat_log_printf ("  default_value . . . . : '%s'", ptr_fset_option->default_value);
        weechat_log_printf ("  value . . . . . . . . : '%s'", ptr_fset_option->value);
        weechat_log_printf ("  parent_value. . . . . : '%s'", ptr_fset_option->parent_value);
        weechat_log_printf ("  min . . . . . . . . . : '%s'", ptr_fset_option->min);
        weechat_log_printf ("  max . . . . . . . . . : '%s'", ptr_fset_option->max);
        weechat_log_printf ("  description . . . . . : '%s'", ptr_fset_option->description);
        weechat_log_printf ("  string_values . . . . : '%s'", ptr_fset_option->string_values);
        weechat_log_printf ("  allowed_values. . . . : '%s'", ptr_fset_option->allowed_values);
        weechat_log_printf ("  marked. . . . . . . . : %d", ptr_fset_option->marked);
    }
}

/*
 * Initialize fset list of options.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
fset_option_init (void)
{
    fset_options = fset_option_get_arraylist_options ();
    if (!fset_options)
        return 0;
    fset_option_count_marked = 0;

    fset_option_max_length = fset_option_get_max_length ();
    if (!fset_option_max_length)
    {
        weechat_arraylist_free (fset_options);
        return 0;
    }

    fset_option_filter_hashtable_pointers = weechat_hashtable_new (
        8,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_POINTER,
        NULL, NULL);
    if (!fset_option_filter_hashtable_pointers)
    {
        weechat_arraylist_free (fset_options);
        free (fset_option_max_length);
        return 0;
    }
    fset_option_filter_hashtable_extra_vars = weechat_hashtable_new (
        128,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    if (!fset_option_filter_hashtable_extra_vars)
    {
        weechat_arraylist_free (fset_options);
        free (fset_option_max_length);
        weechat_hashtable_free (fset_option_filter_hashtable_pointers);
        return 0;
    }
    fset_option_filter_hashtable_options = weechat_hashtable_new (
        8,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    if (!fset_option_filter_hashtable_options)
    {
        weechat_arraylist_free (fset_options);
        free (fset_option_max_length);
        weechat_hashtable_free (fset_option_filter_hashtable_pointers);
        weechat_hashtable_free (fset_option_filter_hashtable_extra_vars);
        return 0;
    }
    weechat_hashtable_set (fset_option_filter_hashtable_options,
                           "type", "condition");

    fset_option_timer_options_changed = weechat_hashtable_new (
        128,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_POINTER,
        NULL, NULL);
    if (!fset_option_timer_options_changed)
    {
        weechat_arraylist_free (fset_options);
        free (fset_option_max_length);
        weechat_hashtable_free (fset_option_filter_hashtable_pointers);
        weechat_hashtable_free (fset_option_filter_hashtable_extra_vars);
        weechat_hashtable_free (fset_option_filter_hashtable_options);
        return 0;
    }

    return 1;
}

/*
 * End fset list of options.
 */

void
fset_option_end (void)
{
    if (fset_options)
    {
        weechat_arraylist_free (fset_options);
        fset_options = NULL;
    }
    fset_option_count_marked = 0;
    free (fset_option_max_length);
    fset_option_max_length = NULL;
    free (fset_option_filter);
    fset_option_filter = NULL;
    if (fset_option_filter_hashtable_pointers)
    {
        weechat_hashtable_free (fset_option_filter_hashtable_pointers);
        fset_option_filter_hashtable_pointers = NULL;
    }
    if (fset_option_filter_hashtable_extra_vars)
    {
        weechat_hashtable_free (fset_option_filter_hashtable_extra_vars);
        fset_option_filter_hashtable_extra_vars = NULL;
    }
    if (fset_option_filter_hashtable_options)
    {
        weechat_hashtable_free (fset_option_filter_hashtable_options);
        fset_option_filter_hashtable_options = NULL;
    }
    if (fset_option_timer_options_changed)
    {
        weechat_hashtable_free (fset_option_timer_options_changed);
        fset_option_timer_options_changed = NULL;
    }
    if (fset_option_timer_hook)
    {
        weechat_unhook (fset_option_timer_hook);
        fset_option_timer_hook = NULL;
    }
}
