/*
 * SPDX-FileCopyrightText: 2023-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/>.
 */

/* Functions for IRC list buffer */

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

#include "../weechat-plugin.h"
#include "irc.h"
#include "irc-list.h"
#include "irc-buffer.h"
#include "irc-color.h"
#include "irc-config.h"
#include "irc-input.h"
#include "irc-message.h"
#include "irc-server.h"


struct t_hdata *irc_list_hdata_list_channel = NULL;
struct t_hashtable *irc_list_filter_hashtable_pointers = NULL;
struct t_hashtable *irc_list_filter_hashtable_extra_vars = NULL;
struct t_hashtable *irc_list_filter_hashtable_options = NULL;


/*
 * Compare two channels in list.
 */

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

    /* make C compiler happy */
    (void) arraylist;

    ptr_server = data;
    if (!ptr_server)
        return 1;

    for (i = 0; i < ptr_server->list->sort_fields_count; i++)
    {
        reverse = 1;
        case_sensitive = 1;
        ptr_field = ptr_server->list->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 (irc_list_hdata_list_channel,
                                    pointer1, pointer2,
                                    ptr_field,
                                    case_sensitive);
        rc *= reverse;
        if (rc != 0)
            return rc;
    }

    return 1;
}

/*
 * Free a channel in list.
 */

void
irc_list_free_cb (void *data, struct t_arraylist *arraylist, void *pointer)
{
    struct t_irc_list_channel *ptr_channel;

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

    ptr_channel = (struct t_irc_list_channel *)pointer;
    if (ptr_channel)
    {
        free (ptr_channel->name);
        free (ptr_channel->name2);
        free (ptr_channel->topic);
        free (ptr_channel);
    }
}

/*
 * Set the local variable "filter" in the list buffer.
 */

void
irc_list_buffer_set_localvar_filter (struct t_gui_buffer *buffer,
                                     struct t_irc_server *server)
{
    if (!buffer || !server)
        return;

    weechat_buffer_set (buffer, "localvar_set_filter",
                        (server->list->filter) ? server->list->filter : "*");
}

/*
 * Set filter for list of channels.
 */

void
irc_list_set_filter (struct t_irc_server *server, const char *filter)
{
    if (server->list->filter)
    {
        free (server->list->filter);
        server->list->filter = NULL;
    }

    server->list->filter = (filter && (strcmp (filter, "*") != 0)) ?
        strdup (filter) : NULL;

    irc_list_buffer_set_localvar_filter (server->list->buffer, server);
}

/*
 * Set sort for list of channels.
 *
 */

void
irc_list_set_sort (struct t_irc_server *server, const char *sort)
{
    if (server->list->sort)
    {
        free (server->list->sort);
        server->list->sort = NULL;
    }
    if (server->list->sort_fields)
    {
        weechat_string_free_split (server->list->sort_fields);
        server->list->sort_fields = NULL;
    }
    server->list->sort_fields_count = 0;

    server->list->sort = strdup (
        (sort && sort[0]) ?
        sort : weechat_config_string (irc_config_look_list_buffer_sort));

    if (server->list->sort)
    {
        server->list->sort_fields = weechat_string_split (
            server->list->sort,
            ",",
            NULL,
            WEECHAT_STRING_SPLIT_STRIP_LEFT
            | WEECHAT_STRING_SPLIT_STRIP_RIGHT
            | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
            0,
            &server->list->sort_fields_count);
    }
}

/*
 * Add the properties of an irc list channel in a hashtable
 * (keys and values must be strings).
 */

void
irc_list_add_channel_in_hashtable (struct t_hashtable *hashtable,
                                   struct t_irc_list_channel *channel)
{
    char str_number[32];

    weechat_hashtable_set (hashtable, "name", channel->name);
    weechat_hashtable_set (hashtable, "name2", channel->name2);
    snprintf (str_number, sizeof (str_number), "%d", channel->users);
    weechat_hashtable_set (hashtable, "users", str_number);
    weechat_hashtable_set (hashtable, "topic", channel->topic);
}

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

int
irc_list_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;
}

/*
 * Check if a channel matches filter.
 *
 * Return:
 *   1: channel matches filter
 *   0: channel does NOT match filter
 */

int
irc_list_channel_match_filter (struct t_irc_server *server,
                               struct t_irc_list_channel *channel)
{
    char *error, *result;
    long number;
    int match;

    /* no filter? then any channel is matching */
    if (!server->list->filter)
        return 1;

    if (strncmp (server->list->filter, "c:", 2) == 0)
    {
        /* filter by evaluated condition */
        weechat_hashtable_set (irc_list_filter_hashtable_pointers,
                               "irc_list_channel", channel);
        irc_list_add_channel_in_hashtable (irc_list_filter_hashtable_extra_vars,
                                           channel);
        result = weechat_string_eval_expression (
            server->list->filter + 2,
            irc_list_filter_hashtable_pointers,
            irc_list_filter_hashtable_extra_vars,
            irc_list_filter_hashtable_options);
        match = (result && (strcmp (result, "1") == 0)) ? 1 : 0;
        free (result);
        return match;
    }

    if (strncmp (server->list->filter, "n:", 2) == 0)
    {
        /* filter by channel name */
        if (channel->name
            && irc_list_string_match (channel->name, server->list->filter + 2))
        {
            return 1;
        }
    }
    else if (strncmp (server->list->filter, "t:", 2) == 0)
    {
        /* filter by topic */
        if (channel->topic
            && irc_list_string_match (channel->topic, server->list->filter + 2))
        {
            return 1;
        }
    }
    else if (strncmp (server->list->filter, "u:>", 3) == 0)
    {
        /* filter by users (> N)*/
        error = NULL;
        number = strtol (server->list->filter + 3, &error, 10);
        if (error && !error[0] && channel->users > (int)number)
            return 1;
    }
    else if (strncmp (server->list->filter, "u:<", 3) == 0)
    {
        /* filter by users (< N)*/
        error = NULL;
        number = strtol (server->list->filter + 3, &error, 10);
        if (error && !error[0] && channel->users < (int)number)
            return 1;
    }
    else if (strncmp (server->list->filter, "u:", 2) == 0)
    {
        /* filter by users */
        error = NULL;
        number = strtol (server->list->filter + 2, &error, 10);
        if (error && !error[0] && channel->users >= (int)number)
            return 1;
    }
    else
    {
        if (channel->name
            && irc_list_string_match (channel->name, server->list->filter))
        {
            return 1;
        }
        if (channel->topic
            && irc_list_string_match (channel->topic, server->list->filter))
        {
            return 1;
        }
    }

    return 0;
}

/*
 * Filter channels: apply filter and use sort to build the list
 * "filter_channels" that are pointers to t_irc_list_channel structs
 * stored in main list "channels".
 */

void
irc_list_filter_channels (struct t_irc_server *server)
{
    struct t_irc_list_channel *ptr_channel;
    int i, list_size;

    if (server->list->filter_channels)
    {
        weechat_arraylist_clear (server->list->filter_channels);
    }
    else
    {
        server->list->filter_channels = weechat_arraylist_new (
            16, 1, 0,
            &irc_list_compare_cb, server,
            NULL, NULL);
    }

    if (!server->list->sort)
    {
        irc_list_set_sort (
            server,
            weechat_config_string (irc_config_look_list_buffer_sort));
    }

    list_size = weechat_arraylist_size (server->list->channels);
    for (i = 0; i < list_size; i++)
    {
        ptr_channel = (struct t_irc_list_channel *)weechat_arraylist_get (
            server->list->channels, i);
        if (!ptr_channel)
            continue;
        if (irc_list_channel_match_filter (server, ptr_channel))
            weechat_arraylist_add (server->list->filter_channels, ptr_channel);
    }
}

/*
 * Parse output of redirected /list (string with raw IRC messages separated
 * by newlines) and return an arraylist of parsed channels, or NULL if no
 * valid message (322) was found in output.
 *
 * Return:
 *   1: OK
 *   0: error
 */

int
irc_list_parse_messages (struct t_irc_server *server, const char *output)
{
    struct t_irc_list_channel *channel;
    char **irc_msgs, *command, **params, *error;
    const char *ptr_name;
    int i, count_irc_msgs, num_params, length, keep_colors;
    long number;

    if (server->list->channels)
    {
        weechat_arraylist_free (server->list->channels);
        server->list->channels = NULL;
    }

    irc_msgs = weechat_string_split (output, "\n", NULL, 0, 0, &count_irc_msgs);
    if (!irc_msgs)
        return 0;

    server->list->channels = weechat_arraylist_new (
        16, 0, 1,
        NULL, NULL,
        &irc_list_free_cb, NULL);
    if (!server->list->channels)
    {
        weechat_string_free_split (irc_msgs);
        return 0;
    }

    server->list->name_max_length = 0;

    keep_colors = (weechat_config_boolean (
                       irc_config_look_list_buffer_topic_strip_colors)) ?
        0 : 1;

    for (i = 0; i < count_irc_msgs; i++)
    {
        irc_message_parse (server, irc_msgs[i],
                           NULL,  /* tags */
                           NULL,  /* message_without_tags */
                           NULL,  /* nick */
                           NULL,  /* user */
                           NULL,  /* host */
                           &command,
                           NULL,  /* channel */
                           NULL,  /* arguments */
                           NULL,  /* text */
                           &params,
                           &num_params,
                           NULL,  /* pos_command */
                           NULL,  /* pos_arguments */
                           NULL,  /* pos_channel */
                           NULL);  /* pos_text */
        if (command
            && (strcmp (command, "322") == 0)
            && params
            && (num_params >= 3))
        {
            channel = malloc (sizeof (*channel));
            if (channel)
            {
                channel->name = strdup (params[1]);
                ptr_name = params[1] + 1;
                while (ptr_name[0] && (ptr_name[0] == params[1][0]))
                {
                    ptr_name++;
                }
                channel->name2 = strdup (ptr_name);
                error = NULL;
                number = strtol (params[2], &error, 10);
                channel->users = (error && !error[0]) ? number : 0;
                channel->topic = (num_params > 3) ?
                    irc_color_decode (params[3], keep_colors) : NULL;
                length = weechat_utf8_strlen_screen (channel->name);
                if (length > server->list->name_max_length)
                    server->list->name_max_length = length;
                weechat_arraylist_add (server->list->channels, channel);
            }
        }
        free (command);
        weechat_string_free_split (params);
    }

    weechat_string_free_split (irc_msgs);

    irc_list_filter_channels (server);

    return 1;
}

/*
 * Set title of list buffer.
 */

void
irc_list_buffer_set_title (struct t_irc_server *server)
{
    int num_channels, num_channels_total;
    char str_title[8192];

    if (!server || !server->list->buffer)
        return;

    num_channels = (server->list->filter_channels) ?
        weechat_arraylist_size (server->list->filter_channels) : 0;
    num_channels_total = (server->list->channels) ?
        weechat_arraylist_size (server->list->channels) : 0;

    snprintf (str_title, sizeof (str_title),
              _("%d channels (total: %d) | Filter: %s | Sort: %s | "
                "Keys: ctrl+j=join channel | "
                "Input: $=refresh, s:x,y=sort, words=filter, *=reset filter, "
                "q=close buffer"),
              num_channels,
              num_channels_total,
              (server->list->filter) ? server->list->filter : "*",
              (server->list->sort) ? server->list->sort : "");

    weechat_buffer_set (server->list->buffer, "title", str_title);
}

/*
 * Display a line.
 */

void
irc_list_display_line (struct t_irc_server *server, int line)
{
    struct t_irc_list_channel *ptr_channel;
    const char *ptr_color;
    char str_spaces[1024], color[256];
    int num_spaces;

    ptr_channel = (struct t_irc_list_channel *)weechat_arraylist_get (
        server->list->filter_channels, line);

    if (!ptr_channel)
    {
        weechat_printf_y (server->list->buffer, line, "");
        return;
    }

    /* line color */
    if (line == server->list->selected_line)
    {
        snprintf (color, sizeof (color),
                  "%s,%s",
                  weechat_config_string (irc_config_color_list_buffer_line_selected),
                  weechat_config_string (irc_config_color_list_buffer_line_selected_bg));
        ptr_color = weechat_color (color);
    }
    else
    {
        ptr_color = NULL;
    }

    /* channel name */
    str_spaces[0] = '\0';
    num_spaces = server->list->name_max_length
        - weechat_utf8_strlen_screen (ptr_channel->name);
    if (num_spaces > 0)
    {
        if (num_spaces >= (int)sizeof (str_spaces))
            num_spaces = sizeof (str_spaces) - 1;
        memset (str_spaces, ' ', num_spaces);
        str_spaces[num_spaces] = '\0';
    }

    /* display the line */
    weechat_printf_y (
        server->list->buffer,
        line,
        "%s%s%s %7d  %s",
        (ptr_color) ? ptr_color : "",
        ptr_channel->name,
        str_spaces,
        ptr_channel->users,
        ptr_channel->topic);
}

/*
 * Update list of channels in list buffer.
 */

void
irc_list_buffer_refresh (struct t_irc_server *server, int clear)
{
    int num_channels, num_filter_channels, i;

    if (!server || !server->list->buffer)
        return;

    num_channels = weechat_arraylist_size (server->list->channels);
    num_filter_channels = weechat_arraylist_size (server->list->filter_channels);

    if (clear || (num_channels == 0))
    {
        weechat_buffer_clear (server->list->buffer);
        server->list->selected_line = 0;
    }

    if (num_channels == 0)
    {
        weechat_printf_y (
            server->list->buffer, 1,
            "%s",
            _("Empty list of channels, try \"$\" to refresh list"));
    }
    else
    {
        for (i = 0; i < num_filter_channels; i++)
        {
            irc_list_display_line (server, i);
        }
    }

    irc_list_buffer_set_title (server);
}

/*
 * Set current selected line.
 */

void
irc_list_set_current_line (struct t_irc_server *server, int line)
{
    int old_line;

    if ((line >= 0) && (line < weechat_arraylist_size (server->list->filter_channels)))
    {
        old_line = server->list->selected_line;
        server->list->selected_line = line;

        if (old_line != server->list->selected_line)
            irc_list_display_line (server, old_line);
        irc_list_display_line (server, server->list->selected_line);

        irc_list_buffer_set_title (server);
    }
}

/*
 * Get info about a window.
 */

void
irc_list_get_window_info (struct t_gui_window *window,
                          int *start_line_y, int *chat_height)
{
    struct t_hdata *hdata_window, *hdata_window_scroll, *hdata_line;
    struct t_hdata *hdata_line_data;
    void *window_scroll, *start_line, *line_data;

    hdata_window = weechat_hdata_get ("window");
    hdata_window_scroll = weechat_hdata_get ("window_scroll");
    hdata_line = weechat_hdata_get ("line");
    hdata_line_data = weechat_hdata_get ("line_data");
    *start_line_y = 0;
    window_scroll = weechat_hdata_pointer (hdata_window, window, "scroll");
    if (window_scroll)
    {
        start_line = weechat_hdata_pointer (hdata_window_scroll, window_scroll,
                                            "start_line");
        if (start_line)
        {
            line_data = weechat_hdata_pointer (hdata_line, start_line, "data");
            if (line_data)
            {
                *start_line_y = weechat_hdata_integer (hdata_line_data,
                                                       line_data, "y");
            }
        }
    }
    *chat_height = weechat_hdata_integer (hdata_window, window,
                                          "win_chat_height");
}

/*
 * Check if current line is outside window and adjusts scroll if needed.
 */

void
irc_list_check_line_outside_window (struct t_irc_server *server)
{
    struct t_gui_window *window;
    int start_line_y, chat_height;
    int selected_y;
    char str_command[256];

    window = weechat_window_search_with_buffer (server->list->buffer);
    if (!window)
        return;

    irc_list_get_window_info (window, &start_line_y, &chat_height);

    selected_y = server->list->selected_line;

    if ((start_line_y > selected_y)
        || (start_line_y < selected_y - chat_height + 1))
    {
        snprintf (str_command, sizeof (str_command),
                  "/window scroll -window %d %s%d",
                  weechat_window_get_integer (window, "number"),
                  (start_line_y > selected_y) ? "-" : "+",
                  (start_line_y > selected_y) ?
                  start_line_y - selected_y :
                  selected_y - start_line_y - chat_height + 1);
        weechat_command (server->list->buffer, str_command);
    }
}

/*
 * Callback for signal "window_scrolled".
 */

int
irc_list_window_scrolled_cb (const void *pointer, void *data,
                             const char *signal, const char *type_data,
                             void *signal_data)
{
    struct t_gui_buffer *ptr_buffer;
    struct t_irc_server *ptr_server;
    int start_line_y, chat_height, line, num_channels;

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

    /* search the /list buffer */
    ptr_buffer = weechat_window_get_pointer (signal_data, "buffer");
    for (ptr_server = irc_servers; ptr_server;
         ptr_server = ptr_server->next_server)
    {
        if (ptr_server->list->buffer == ptr_buffer)
            break;
    }
    if (!ptr_server)
        return WEECHAT_RC_OK;

    irc_list_get_window_info (signal_data, &start_line_y, &chat_height);

    line = ptr_server->list->selected_line;
    while (line < start_line_y)
    {
        line += chat_height;
    }
    while (line >= start_line_y + chat_height)
    {
        line -= chat_height;
    }
    if (line < start_line_y)
        line = start_line_y + 1;

    num_channels = weechat_arraylist_size (ptr_server->list->filter_channels);
    if ((num_channels > 0) && (line >= num_channels))
        line = num_channels - 1;

    irc_list_set_current_line (ptr_server, line);

    return WEECHAT_RC_OK;
}

/*
 * Move N lines up/down in buffer
 * (negative lines = move up, positive lines = move down).
 */

void
irc_list_move_line_relative (struct t_irc_server *server, int num_lines)
{
    int num_channels, line;

    num_channels = weechat_arraylist_size (server->list->filter_channels);
    if (num_channels == 0)
        return;

    line = server->list->selected_line + num_lines;
    if (line < 0)
        line = 0;
    if ((num_channels > 0) && (line >= num_channels))
        line = num_channels - 1;
    if (line != server->list->selected_line)
    {
        irc_list_set_current_line (server, line);
        irc_list_check_line_outside_window (server);
    }
}

/*
 * Move to line N (0 = first line, -1 = last line).
 */

void
irc_list_move_line_absolute (struct t_irc_server *server, int line_number)
{
    int num_channels, line;

    num_channels = weechat_arraylist_size (server->list->filter_channels);
    if (num_channels == 0)
        return;

    line = line_number;
    if (line < 0)
        line = (num_channels > 0) ? num_channels - 1 : 0;
    if ((num_channels > 0) && (line >= num_channels))
        line = num_channels - 1;
    if (line != server->list->selected_line)
    {
        irc_list_set_current_line (server, line);
        irc_list_check_line_outside_window (server);
    }
}

/*
 * Scroll horizontally with percent
 * (negative: scroll to the left, positive: scroll to the right).
 */

void
irc_list_scroll_horizontal (struct t_irc_server *server, int percent)
{
    struct t_gui_window *ptr_window;
    char str_command[512];

    if (percent < -100)
        percent = -100;
    else if (percent > 100)
        percent = 100;

    ptr_window = weechat_window_search_with_buffer (server->list->buffer);
    if (!ptr_window)
        return;

    snprintf (str_command, sizeof (str_command),
              "/window scroll_horiz -window %d %d%%",
              weechat_window_get_integer (ptr_window, "number"),
              percent);
    weechat_command (server->list->buffer, str_command);
}

/*
 * Join channel on current selected line.
 */

void
irc_list_join_channel (struct t_irc_server *server)
{
    struct t_irc_list_channel *ptr_channel;
    int num_channels;
    char str_command[1024];

    num_channels = weechat_arraylist_size (server->list->filter_channels);

    if ((num_channels == 0) || (server->list->selected_line >= num_channels))
        return;

    ptr_channel = (struct t_irc_list_channel *)weechat_arraylist_get (
        server->list->filter_channels,
        server->list->selected_line);
    if (!ptr_channel)
        return;

    snprintf (str_command, sizeof (str_command),
              "/join %s", ptr_channel->name);

    weechat_command (server->list->buffer, str_command);
}

/*
 * Callback for input data in list buffer.
 */

int
irc_list_buffer_input_data (struct t_gui_buffer *buffer, const char *input_data)
{
    struct t_irc_server *ptr_server;
    const char *ptr_server_name, *ptr_input;
    int i;
    char *actions[][2] = {
        { "<<", "/list -go 0"   },
        { ">>", "/list -go end" },
        { "<",  "/list -left"   },
        { ">",  "/list -right"  },
        { NULL, NULL            },
    };

    /* close buffer */
    if (strcmp (input_data, "q") == 0)
    {
        weechat_buffer_close (buffer);
        return WEECHAT_RC_OK;
    }

    ptr_server_name = weechat_buffer_get_string (buffer, "localvar_server");
    if (!ptr_server_name)
        return WEECHAT_RC_OK;

    ptr_server = irc_server_search (ptr_server_name);
    if (!ptr_server)
        return WEECHAT_RC_OK;

    /* refresh buffer */
    if (strcmp (input_data, "$") == 0)
    {
        weechat_command (ptr_server->list->buffer, "/list");
        return WEECHAT_RC_OK;
    }

    /* join channel */
    if (strcmp (input_data, "j") == 0)
    {
        irc_list_join_channel (ptr_server);
        return WEECHAT_RC_OK;
    }

    /* change sort of channels */
    if (strncmp (input_data, "s:", 2) == 0)
    {
        irc_list_set_sort (ptr_server, input_data + 2);
        irc_list_filter_channels (ptr_server);
        irc_list_buffer_refresh (ptr_server, 1);
        weechat_buffer_set (buffer, "display", "1");
        return WEECHAT_RC_OK;
    }

    /* execute action */
    for (i = 0; actions[i][0]; i++)
    {
        if (strcmp (input_data, actions[i][0]) == 0)
        {
            weechat_command (buffer, actions[i][1]);
            return WEECHAT_RC_OK;
        }
    }

    /* filter channels with given text */
    ptr_input = input_data;
    while (ptr_input[0] == ' ')
    {
        ptr_input++;
    }
    if (ptr_input[0])
    {
        irc_list_set_filter (ptr_server, ptr_input);
        irc_list_filter_channels (ptr_server);
        irc_list_buffer_refresh (ptr_server, 1);
        weechat_buffer_set (buffer, "display", "1");
    }

    return WEECHAT_RC_OK;
}

/*
 * Create buffer with list of channels for a server.
 *
 * Return pointer to newly created buffer, NULL if error.
 */

struct t_gui_buffer *
irc_list_create_buffer (struct t_irc_server *server)
{
    struct t_hashtable *buffer_props;
    struct t_gui_buffer *buffer;
    char buffer_name[1024], str_number[32];
    int buffer_position, current_buffer_number;

    buffer_props = weechat_hashtable_new (
        32,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    if (buffer_props)
    {
        weechat_hashtable_set (buffer_props, "type", "free");
        weechat_hashtable_set (buffer_props, "localvar_set_type", "list");
        weechat_hashtable_set (buffer_props, "localvar_set_server", server->name);
        weechat_hashtable_set (buffer_props, "localvar_set_channel", server->name);
        weechat_hashtable_set (buffer_props, "localvar_set_no_log", "1");
        /* disable all highlights on this buffer */
        weechat_hashtable_set (buffer_props, "highlight_words", "-");
        /* set keys on buffer */
        weechat_hashtable_set (buffer_props, "key_bind_up", "/list -up");
        weechat_hashtable_set (buffer_props, "key_bind_down", "/list -down");
        weechat_hashtable_set (buffer_props, "key_bind_meta-home", "/list -go 0");
        weechat_hashtable_set (buffer_props, "key_bind_meta-end", "/list -go end");
        weechat_hashtable_set (buffer_props, "key_bind_f11", "/list -left");
        weechat_hashtable_set (buffer_props, "key_bind_f12", "/list -right");
        weechat_hashtable_set (buffer_props, "key_bind_ctrl-j", "/list -join");
    }

    current_buffer_number = weechat_buffer_get_integer (
        weechat_current_buffer (), "number");

    snprintf (buffer_name, sizeof (buffer_name), "list_%s", server->name);

    buffer = weechat_buffer_new_props (
        buffer_name,
        buffer_props,
        &irc_input_data_cb, NULL, NULL,
        &irc_buffer_close_cb, NULL, NULL);

    weechat_hashtable_free (buffer_props);

    irc_list_buffer_set_localvar_filter (buffer, server);

    if (weechat_buffer_get_integer (buffer, "layout_number") < 1)
    {
        buffer_position = weechat_config_enum (irc_config_look_new_list_position);
        switch (buffer_position)
        {
            case IRC_CONFIG_LOOK_BUFFER_POSITION_NONE:
                /* do nothing */
                break;
            case IRC_CONFIG_LOOK_BUFFER_POSITION_NEXT:
                /* move buffer to current number + 1 */
                snprintf (str_number, sizeof (str_number),
                          "%d", current_buffer_number + 1);
                weechat_buffer_set (buffer, "number", str_number);
                break;
            case IRC_CONFIG_LOOK_BUFFER_POSITION_NEAR_SERVER:
                /* move buffer after last channel/pv of server */
                irc_buffer_move_near_server (
                    server,
                    1,  /* list_buffer */
                    -1,  /* channel_type */
                    buffer);
                break;
        }
    }

    return buffer;
}

/*
 * Callback for redirected /list command.
 */

int
irc_list_hsignal_redirect_list_cb (const void *pointer,
                                   void *data,
                                   const char *signal,
                                   struct t_hashtable *hashtable)
{
    struct t_irc_server *ptr_server;
    const char *ptr_error, *ptr_server_name, *ptr_output;

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

    ptr_error = weechat_hashtable_get (hashtable, "error");
    if (ptr_error && ptr_error[0])
    {
        weechat_printf (
            NULL,
            _("%s%s: error in redirection of /list: %s"),
            weechat_prefix ("error"), IRC_PLUGIN_NAME, ptr_error);
        return WEECHAT_RC_OK;
    }

    ptr_server_name = weechat_hashtable_get (hashtable, "server");
    if (!ptr_server_name)
        return WEECHAT_RC_OK;

    ptr_server = irc_server_search (ptr_server_name);
    if (!ptr_server || !ptr_server->buffer)
        return WEECHAT_RC_OK;

    ptr_output = weechat_hashtable_get (hashtable, "output");
    if (!ptr_output)
        return WEECHAT_RC_OK;

    if (!irc_list_hdata_list_channel)
    {
        irc_list_hdata_list_channel = weechat_hdata_get ("irc_list_channel");
        if (!irc_list_hdata_list_channel)
            return WEECHAT_RC_OK;
    }

    irc_list_parse_messages (ptr_server, ptr_output);
    if (!ptr_server->list->channels)
        return WEECHAT_RC_OK;

    irc_list_buffer_refresh (ptr_server, 1);

    return WEECHAT_RC_OK;
}

/*
 * Export channels currently displayed in /list buffer.
 *
 * Return:
 *   1: export OK
 *   0: error
 */

int
irc_list_export (struct t_irc_server *server, const char *filename)
{
    int num_filter_channels, i;
    char *filename2, *line;
    FILE *file;
    struct t_irc_list_channel *ptr_channel;
    struct t_hashtable *hashtable_pointers, *hashtable_extra_vars;

    if (!server || !server->list->buffer)
        return 0;

    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);

    weechat_hashtable_set (hashtable_pointers, "irc_server", server);

    num_filter_channels = weechat_arraylist_size (server->list->filter_channels);
    for (i = 0; i < num_filter_channels; i++)
    {
        ptr_channel = (struct t_irc_list_channel *)weechat_arraylist_get (
            server->list->filter_channels, i);
        if (!ptr_channel)
            continue;

        weechat_hashtable_set (hashtable_pointers, "irc_list_channel", ptr_channel);

        irc_list_add_channel_in_hashtable (hashtable_extra_vars, ptr_channel);

        line = weechat_string_eval_expression (
            weechat_config_string (irc_config_look_list_buffer_format_export),
            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;
}

/*
 * Reset lists used by list buffer.
 */

void
irc_list_reset (struct t_irc_server *server)
{
    if (!server)
        return;

    if (server->list->channels)
        weechat_arraylist_clear (server->list->channels);
    if (server->list->filter_channels)
        weechat_arraylist_clear (server->list->filter_channels);
    server->list->name_max_length = 0;
    if (!server->list->sort)
    {
        irc_list_set_sort (
            server,
            weechat_config_string (irc_config_look_list_buffer_sort));
    }
    server->list->selected_line = 0;
}

/*
 * Free a list structure in a server.
 */

struct t_irc_list *
irc_list_alloc (void)
{
    struct t_irc_list *list;

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

    list->buffer = NULL;
    list->channels = NULL;
    list->filter_channels = NULL;
    list->name_max_length = 0;
    list->filter = NULL;
    list->sort = NULL;
    list->sort_fields = NULL;
    list->sort_fields_count = 0;
    list->selected_line = 0;

    return list;
}

/*
 * Free data in a list structure.
 */

void
irc_list_free_data (struct t_irc_server *server)
{
    if (!server || !server->list)
        return;

    if (server->list->channels)
    {
        weechat_arraylist_free (server->list->channels);
        server->list->channels = NULL;
    }
    if (server->list->filter_channels)
    {
        weechat_arraylist_free (server->list->filter_channels);
        server->list->filter_channels = NULL;
    }
    server->list->name_max_length = 0;
    if (server->list->filter)
    {
        free (server->list->filter);
        server->list->filter = NULL;
    }
    if (server->list->sort)
    {
        free (server->list->sort);
        server->list->sort = NULL;
    }
    if (server->list->sort_fields)
    {
        weechat_string_free_split (server->list->sort_fields);
        server->list->sort_fields = NULL;
    }
    server->list->sort_fields_count = 0;
    server->list->selected_line = 0;
}

/*
 * Free a list structure in a server.
 */

void
irc_list_free (struct t_irc_server *server)
{
    if (!server || !server->list)
        return;

    if (server->list->buffer)
        weechat_buffer_close (server->list->buffer);

    irc_list_free_data (server);

    free (server->list);
    server->list = NULL;
}

/*
 * Return hdata for irc_list_channel.
 */

struct t_hdata *
irc_list_hdata_list_channel_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_irc_list_channel, name, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list_channel, name2, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list_channel, users, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list_channel, topic, STRING, 0, NULL, NULL);
    }
    return hdata;
}

/*
 * Return hdata for irc_list.
 */

struct t_hdata *
irc_list_hdata_list_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_irc_list, buffer, POINTER, 0, NULL, "buffer");
        WEECHAT_HDATA_VAR(struct t_irc_list, channels, POINTER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, filter_channels, POINTER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, name_max_length, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, filter, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, sort, STRING, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, sort_fields, POINTER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, sort_fields_count, INTEGER, 0, NULL, NULL);
        WEECHAT_HDATA_VAR(struct t_irc_list, selected_line, INTEGER, 0, NULL, NULL);
    }
    return hdata;
}

/*
 * Callback called when a mouse action occurs in irc list buffer.
 */

int
irc_list_mouse_hsignal_cb (const void *pointer, void *data, const char *signal,
                           struct t_hashtable *hashtable)
{
    const char *ptr_key, *ptr_chat_line_y, *ptr_buffer_pointer;
    struct t_gui_buffer *ptr_buffer;
    unsigned long value;
    char str_command[1024];
    int rc;

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

    ptr_key = weechat_hashtable_get (hashtable, "_key");
    ptr_buffer_pointer = weechat_hashtable_get (hashtable, "_buffer");
    ptr_chat_line_y = weechat_hashtable_get (hashtable, "_chat_line_y");

    if (!ptr_key || !ptr_buffer_pointer || !ptr_chat_line_y)
        return WEECHAT_RC_OK;

    rc = sscanf (ptr_buffer_pointer, "%lx", &value);
    if ((rc == EOF) || (rc == 0))
        return WEECHAT_RC_OK;
    ptr_buffer = (struct t_gui_buffer *)value;
    if (!ptr_buffer)
        return WEECHAT_RC_OK;

    snprintf (str_command, sizeof (str_command),
              "/list -go %s",
              ptr_chat_line_y);
    weechat_command (ptr_buffer, str_command);

    if (weechat_string_match (ptr_key, "button2*", 1))
        weechat_command (ptr_buffer, "/list -join");

    return WEECHAT_RC_OK;
}

/*
 * Initialize IRC list.
 */

void
irc_list_init (void)
{
    struct t_hashtable *keys;

    irc_list_filter_hashtable_pointers = weechat_hashtable_new (
        8,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_POINTER,
        NULL, NULL);
    irc_list_filter_hashtable_extra_vars = weechat_hashtable_new (
        32,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    irc_list_filter_hashtable_options = weechat_hashtable_new (
        8,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);
    if (irc_list_filter_hashtable_options)
    {
        weechat_hashtable_set (irc_list_filter_hashtable_options,
                               "type", "condition");
    }

    weechat_hook_hsignal (IRC_LIST_MOUSE_HSIGNAL,
                          &irc_list_mouse_hsignal_cb, NULL, NULL);

    keys = weechat_hashtable_new (32,
                                  WEECHAT_HASHTABLE_STRING,
                                  WEECHAT_HASHTABLE_STRING,
                                  NULL, NULL);
    if (keys)
    {
        weechat_hashtable_set (
            keys,
            "@chat(" IRC_PLUGIN_NAME ".list_*):button1",
            "/window ${_window_number};/list -go ${_chat_line_y}");
        weechat_hashtable_set (
            keys,
            "@chat(" IRC_PLUGIN_NAME ".list_*):button2*",
            "hsignal:" IRC_LIST_MOUSE_HSIGNAL);
        weechat_hashtable_set (
            keys,
            "@chat(" IRC_PLUGIN_NAME ".list_*):wheelup",
            "/list -up 5");
        weechat_hashtable_set (
            keys,
            "@chat(" IRC_PLUGIN_NAME ".list_*):wheeldown",
            "/list -down 5");
        weechat_hashtable_set (keys, "__quiet", "1");
        weechat_key_bind ("mouse", keys);
        weechat_hashtable_free (keys);
    }
}

/*
 * End IRC list.
 */

void
irc_list_end (void)
{
    if (irc_list_filter_hashtable_pointers)
    {
        weechat_hashtable_free (irc_list_filter_hashtable_pointers);
        irc_list_filter_hashtable_pointers = NULL;
    }
    if (irc_list_filter_hashtable_extra_vars)
    {
        weechat_hashtable_free (irc_list_filter_hashtable_extra_vars);
        irc_list_filter_hashtable_extra_vars = NULL;
    }
    if (irc_list_filter_hashtable_options)
    {
        weechat_hashtable_free (irc_list_filter_hashtable_options);
        irc_list_filter_hashtable_options = NULL;
    }
    irc_list_hdata_list_channel = NULL;
}
