/*
 * gui-curses-chat.c - chat display functions for Curses GUI
 *
 * Copyright (C) 2003-2019 Sébastien Helleu <flashcode@flashtux.org>
 *
 * 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/>.
 */

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

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "../../core/weechat.h"
#include "../../core/wee-config.h"
#include "../../core/wee-eval.h"
#include "../../core/wee-hashtable.h"
#include "../../core/wee-hook.h"
#include "../../core/wee-string.h"
#include "../../core/wee-utf8.h"
#include "../../plugins/plugin.h"
#include "../gui-buffer.h"
#include "../gui-chat.h"
#include "../gui-color.h"
#include "../gui-hotlist.h"
#include "../gui-line.h"
#include "../gui-main.h"
#include "../gui-window.h"
#include "gui-curses.h"


/*
 * Gets real width for chat.
 *
 * Returns width - 1 if nicklist is at right, for good copy/paste (without
 * nicklist separator).
 */

int
gui_chat_get_real_width (struct t_gui_window *window)
{
    if ((window->win_chat_width > 1)
        && (window->win_chat_x + window->win_chat_width < gui_window_get_width ()))
        return window->win_chat_width - 1;
    else
        return window->win_chat_width;
}

/*
 * Checks if marker must be displayed after this line.
 *
 * Returns:
 *   1: marker must be displayed after this line
 *   0: marker must not be displayed after this line
 */

int
gui_chat_marker_for_line (struct t_gui_buffer *buffer, struct t_gui_line *line)
{
    struct t_gui_line *last_read_line;

    /* marker is disabled in config? */
    if (CONFIG_INTEGER(config_look_read_marker) != CONFIG_LOOK_READ_MARKER_LINE)
        return 0;

    /* marker is not set for buffer? */
    if (!buffer->lines->last_read_line)
        return 0;

    last_read_line = buffer->lines->last_read_line;
    if (!last_read_line->data->displayed)
        last_read_line = gui_line_get_prev_displayed (last_read_line);

    if (!last_read_line)
        return 0;

    while (line)
    {
        if (last_read_line == line)
        {
            if (CONFIG_BOOLEAN(config_look_read_marker_always_show))
                return 1;
            return (gui_line_get_next_displayed (line) != NULL) ? 1 : 0;
        }

        if (line->data->displayed)
            break;

        line = line->next_line;
    }
    return 0;
}

/*
 * Resets style using color depending on window (active or not) and line (buffer
 * selected in merged buffer or not).
 */

void
gui_chat_reset_style (struct t_gui_window *window, struct t_gui_line *line,
                      int nick_offline, int reset_attributes,
                      int color_inactive_window, int color_inactive_buffer,
                      int color_default)
{
    int color;

    color = color_default;
    if ((window != gui_current_window)
        && CONFIG_BOOLEAN(config_look_color_inactive_window))
    {
        color = color_inactive_window;
    }
    else if (line && !line->data->buffer->active
             && CONFIG_BOOLEAN(config_look_color_inactive_buffer))
    {
        color = color_inactive_buffer;
    }
    else if (nick_offline == 1)
    {
        color = GUI_COLOR_CHAT_NICK_OFFLINE;
    }
    else if (nick_offline > 1)
    {
        color = GUI_COLOR_CHAT_NICK_OFFLINE_HIGHLIGHT;
    }

    if (reset_attributes)
        gui_window_reset_style (GUI_WINDOW_OBJECTS(window)->win_chat, color);
    else
        gui_window_reset_color (GUI_WINDOW_OBJECTS(window)->win_chat, color);
}

/*
 * Deletes all chars from the cursor to the end of the current line.
 */

void
gui_chat_clrtoeol (struct t_gui_window *window)
{
    if (window->win_chat_cursor_y >= window->win_chat_height)
        return;

    wmove (GUI_WINDOW_OBJECTS(window)->win_chat,
           window->win_chat_cursor_y,
           window->win_chat_cursor_x);
    wclrtoeol (GUI_WINDOW_OBJECTS(window)->win_chat);
}

/*
 * Displays a new line.
 */

void
gui_chat_display_new_line (struct t_gui_window *window,
                           int num_lines, int count,
                           int *lines_displayed, int simulate)
{
    if ((count == 0) || (*lines_displayed >= num_lines - count))
        window->win_chat_cursor_y++;

    window->win_chat_cursor_x = 0;
    (*lines_displayed)++;

    if (!simulate)
        gui_chat_clrtoeol (window);
}

/*
 * Displays an horizontal line (marker for data not read).
 */

void
gui_chat_display_horizontal_line (struct t_gui_window *window, int simulate)
{
    int x, size_on_screen;
    char *read_marker_string, *default_string = "- ";

    if (simulate
        || (!simulate && (window->win_chat_cursor_y >= window->win_chat_height)))
        return;

    gui_window_coords_init_line (window, window->win_chat_cursor_y);
    if (CONFIG_INTEGER(config_look_read_marker) == CONFIG_LOOK_READ_MARKER_LINE)
    {
        read_marker_string = CONFIG_STRING(config_look_read_marker_string);
        if (!read_marker_string || !read_marker_string[0])
            read_marker_string = default_string;
        size_on_screen = utf8_strlen_screen (read_marker_string);
        gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                      GUI_COLOR_CHAT_READ_MARKER);
        gui_chat_clrtoeol (window);
        x = 0;
        while (x < gui_chat_get_real_width (window))
        {
            mvwaddstr (GUI_WINDOW_OBJECTS(window)->win_chat,
                       window->win_chat_cursor_y, x,
                       read_marker_string);
            x += size_on_screen;
        }
    }
    window->win_chat_cursor_x = window->win_chat_width;
}

/*
 * Returns next char of a word (for display), special chars like
 * colors/attributes are skipped and optionally applied.
 */

const char *
gui_chat_string_next_char (struct t_gui_window *window, struct t_gui_line *line,
                           const unsigned char *string, int apply_style,
                           int apply_style_inactive,
                           int nick_offline)
{
    if (apply_style && apply_style_inactive)
    {
        if ((window != gui_current_window)
            && CONFIG_BOOLEAN(config_look_color_inactive_window))
        {
            apply_style = 0;
        }
        else if (line && !line->data->buffer->active)
        {
            if (CONFIG_BOOLEAN(config_look_color_inactive_buffer))
                apply_style = 0;
        }
    }
    if (apply_style && nick_offline)
    {
        apply_style = 0;
    }

    while (string[0])
    {
        switch (string[0])
        {
            case GUI_COLOR_COLOR_CHAR:
                string++;
                switch (string[0])
                {
                    case GUI_COLOR_FG_CHAR: /* fg color */
                        string++;
                        gui_window_string_apply_color_fg ((unsigned char **)&string,
                                                          (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                        break;
                    case GUI_COLOR_BG_CHAR: /* bg color */
                        string++;
                        gui_window_string_apply_color_bg ((unsigned char **)&string,
                                                          (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                        break;
                    case GUI_COLOR_FG_BG_CHAR: /* fg + bg color */
                        string++;
                        gui_window_string_apply_color_fg_bg ((unsigned char **)&string,
                                                             (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                        break;
                    case GUI_COLOR_EXTENDED_CHAR: /* pair number */
                        string++;
                        gui_window_string_apply_color_pair ((unsigned char **)&string,
                                                            (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                        break;
                    case GUI_COLOR_EMPHASIS_CHAR: /* emphasis */
                        string++;
                        if (apply_style)
                            gui_window_toggle_emphasis ();
                        break;
                    case GUI_COLOR_BAR_CHAR: /* bar color */
                        string++;
                        switch (string[0])
                        {
                            case GUI_COLOR_BAR_FG_CHAR:
                            case GUI_COLOR_BAR_DELIM_CHAR:
                            case GUI_COLOR_BAR_BG_CHAR:
                            case GUI_COLOR_BAR_START_INPUT_CHAR:
                            case GUI_COLOR_BAR_START_INPUT_HIDDEN_CHAR:
                            case GUI_COLOR_BAR_MOVE_CURSOR_CHAR:
                            case GUI_COLOR_BAR_START_ITEM:
                            case GUI_COLOR_BAR_START_LINE_ITEM:
                                string++;
                                break;
                        }
                        break;
                    case GUI_COLOR_RESET_CHAR: /* reset color (keep attributes) */
                        string++;
                        if (apply_style)
                        {
                            if (apply_style_inactive)
                            {
                                gui_chat_reset_style (window, line, 0, 0,
                                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                                      GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                                      GUI_COLOR_CHAT);
                            }
                            else
                            {
                                gui_window_reset_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                                        GUI_COLOR_CHAT);
                            }
                        }
                        break;
                    default:
                        gui_window_string_apply_color_weechat ((unsigned char **)&string,
                                                               (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                        break;
                }
                break;
            case GUI_COLOR_SET_ATTR_CHAR:
                string++;
                gui_window_string_apply_color_set_attr ((unsigned char **)&string,
                                                        (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                break;
            case GUI_COLOR_REMOVE_ATTR_CHAR:
                string++;
                gui_window_string_apply_color_remove_attr ((unsigned char **)&string,
                                                           (apply_style) ? GUI_WINDOW_OBJECTS(window)->win_chat : NULL);
                break;
            case GUI_COLOR_RESET_CHAR:
                string++;
                if (apply_style)
                {
                    if (apply_style_inactive)
                    {
                        gui_chat_reset_style (window, line, 0, 1,
                                              GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                              GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                              GUI_COLOR_CHAT);
                    }
                    else
                    {
                        gui_window_reset_style (GUI_WINDOW_OBJECTS(window)->win_chat,
                                                GUI_COLOR_CHAT);
                    }
                }
                break;
            default:
                return (char *)string;
                break;
        }
    }

    /* nothing found except color/attrib codes, so return NULL */
    return NULL;
}

/*
 * Displays word on chat buffer, letter by letter, special chars like
 * colors/attributes are interpreted.
 *
 * Returns number of chars displayed on screen.
 */

int
gui_chat_display_word_raw (struct t_gui_window *window, struct t_gui_line *line,
                           const char *string,
                           int max_chars_on_screen, int simulate,
                           int apply_style_inactive,
                           int nick_offline)
{
    char *next_char, *output, utf_char[16], *ptr_char;
    int x, chars_displayed, display_char, size_on_screen;

    if (!simulate)
    {
        wmove (GUI_WINDOW_OBJECTS(window)->win_chat,
               window->win_chat_cursor_y,
               window->win_chat_cursor_x);
    }

    chars_displayed = 0;
    x = window->win_chat_cursor_x;

    while (string && string[0])
    {
        string = gui_chat_string_next_char (window, line,
                                            (unsigned char *)string, 1,
                                            apply_style_inactive,
                                            nick_offline);
        if (!string)
            return chars_displayed;

        next_char = (char *)utf8_next_char (string);
        if (next_char)
        {
            ptr_char = utf_char;

            memcpy (utf_char, string, next_char - string);
            utf_char[next_char - string] = '\0';

            if (!gui_chat_utf_char_valid (utf_char))
                snprintf (utf_char, sizeof (utf_char), " ");
            else if (utf_char[0] == '\t')
                ptr_char = config_tab_spaces;

            display_char = (window->buffer->type != GUI_BUFFER_TYPE_FREE)
                || (x >= window->scroll->start_col);

            size_on_screen = utf8_strlen_screen (ptr_char);
            if ((max_chars_on_screen > 0)
                && (chars_displayed + size_on_screen > max_chars_on_screen))
            {
                return chars_displayed;
            }
            if (display_char && (size_on_screen >= 0))
            {
                if (!simulate)
                {
                    output = string_iconv_from_internal (NULL, ptr_char);
                    waddstr (GUI_WINDOW_OBJECTS(window)->win_chat,
                             (output) ? output : ptr_char);
                    if (output)
                        free (output);

                    if (gui_window_current_emphasis)
                    {
                        gui_window_emphasize (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              x - window->scroll->start_col,
                                              window->win_chat_cursor_y,
                                              size_on_screen);
                    }
                }
                chars_displayed += size_on_screen;
            }
            x += size_on_screen;
        }

        string = next_char;
    }

    return chars_displayed;
}

/*
 * Displays a word on chat buffer.
 *
 * Returns number of chars displayed on screen.
 */

int
gui_chat_display_word (struct t_gui_window *window,
                       struct t_gui_line *line,
                       const char *word, const char *word_end,
                       int prefix, int num_lines, int count,
                       int pre_lines_displayed, int *lines_displayed,
                       int simulate,
                       int apply_style_inactive,
                       int nick_offline)
{
    char *data, *ptr_data, *end_line, saved_char, str_space[] = " ";
    int chars_displayed, pos_saved_char, chars_to_display, num_displayed;
    int length_align;

    chars_displayed = 0;

    if (!word ||
        ((!simulate) && (window->win_chat_cursor_y >= window->win_chat_height)))
        return chars_displayed;

    if (!simulate && (window->win_chat_cursor_y < window->coords_size))
        window->coords[window->win_chat_cursor_y].line = line;

    data = strdup (word);
    if (!data)
        return chars_displayed;

    end_line = data + strlen (data);

    if (word_end && word_end[0])
        data[word_end - word] = '\0';
    else
        word_end = NULL;

    ptr_data = data;
    while (ptr_data && ptr_data[0])
    {
        /* insert spaces for aligning text under time/nick */
        length_align = gui_line_get_align (window->buffer, line, 0, 0);

        /* in the beginning of a line */
        if ((window->win_chat_cursor_x == 0)
            && (*lines_displayed > pre_lines_displayed)
            /* FIXME: modify arbitrary value for non aligning messages on time/nick? */
            && (length_align < (window->win_chat_width - 5)))
        {
            /* in the beginning of a word or in the middle of a word with multiline word align */
            if ((chars_displayed == 0)
                || CONFIG_BOOLEAN(config_look_align_multiline_words))
            {
                window->win_chat_cursor_x += length_align;

                if ((CONFIG_INTEGER(config_look_align_end_of_lines) == CONFIG_LOOK_ALIGN_END_OF_LINES_MESSAGE)
                    && (CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)
                    && CONFIG_STRING(config_look_prefix_suffix)
                    && CONFIG_STRING(config_look_prefix_suffix)[0]
                    && line->data->date > 0)
                {
                    if (!simulate)
                    {
                        gui_window_save_style (GUI_WINDOW_OBJECTS(window)->win_chat);
                        gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                                      GUI_COLOR_CHAT_PREFIX_SUFFIX);
                        gui_window_current_emphasis = 0;
                    }
                    chars_displayed += gui_chat_display_word_raw (window, line,
                                                                  CONFIG_STRING(config_look_prefix_suffix),
                                                                  0, simulate,
                                                                  apply_style_inactive,
                                                                  nick_offline);
                    window->win_chat_cursor_x += gui_chat_strlen_screen (CONFIG_STRING(config_look_prefix_suffix));
                    chars_displayed += gui_chat_display_word_raw (window, line,
                                                                  str_space,
                                                                  0, simulate,
                                                                  apply_style_inactive,
                                                                  nick_offline);
                    window->win_chat_cursor_x += gui_chat_strlen_screen (str_space);
                    if (!simulate)
                        gui_window_restore_style (GUI_WINDOW_OBJECTS(window)->win_chat);
                }
            }
            if (window->win_chat_cursor_y < window->coords_size)
                window->coords[window->win_chat_cursor_y].data = (char *)word + (ptr_data - data);
        }

        chars_to_display = gui_chat_strlen_screen (ptr_data);

        /* too long for current line */
        if (window->win_chat_cursor_x + chars_to_display > gui_chat_get_real_width (window))
        {
            num_displayed = gui_chat_get_real_width (window) - window->win_chat_cursor_x;
            pos_saved_char = gui_chat_string_real_pos (ptr_data, num_displayed,
                                                       1);
            saved_char = ptr_data[pos_saved_char];
            ptr_data[pos_saved_char] = '\0';
            if ((count == 0) || (*lines_displayed >= num_lines - count))
            {
                chars_displayed += gui_chat_display_word_raw (window, line,
                                                              ptr_data,
                                                              0, simulate,
                                                              apply_style_inactive,
                                                              nick_offline);
            }
            else
            {
                chars_displayed += gui_chat_display_word_raw (window, line,
                                                              ptr_data,
                                                              0, 0,
                                                              apply_style_inactive,
                                                              nick_offline);
            }
            ptr_data[pos_saved_char] = saved_char;
            ptr_data += pos_saved_char;
        }
        else
        {
            num_displayed = chars_to_display;
            if ((count == 0) || (*lines_displayed >= num_lines - count))
            {
                chars_displayed += gui_chat_display_word_raw (window, line,
                                                              ptr_data,
                                                              0, simulate,
                                                              apply_style_inactive,
                                                              nick_offline);
            }
            else
            {
                chars_displayed += gui_chat_display_word_raw (window, line,
                                                              ptr_data,
                                                              0, 0,
                                                              apply_style_inactive,
                                                              nick_offline);
            }
            ptr_data += strlen (ptr_data);
        }

        window->win_chat_cursor_x += num_displayed;

        /* display new line? */
        if ((!prefix && (ptr_data >= end_line)) ||
            (((simulate) ||
              (window->win_chat_cursor_y <= window->win_chat_height - 1)) &&
             (window->win_chat_cursor_x > (gui_chat_get_real_width (window) - 1))))
            gui_chat_display_new_line (window, num_lines, count,
                                       lines_displayed, simulate);

        if ((!prefix && (ptr_data >= end_line)) ||
            ((!simulate) && (window->win_chat_cursor_y >= window->win_chat_height)))
            ptr_data = NULL;
    }

    free (data);

    return chars_displayed;
}

/*
 * Displays a message when the day has changed.
 */

void
gui_chat_display_day_changed (struct t_gui_window *window,
                              struct tm *date1, struct tm *date2,
                              int simulate)
{
    char temp_message[1024], message[1024], *message_with_color;
    int year1, year1_last_yday;

    if (simulate
        || (!simulate && (window->win_chat_cursor_y >= window->win_chat_height)))
        return;

    /*
     * if date1 is given, compare date1 and date2; if date2 is date1 + 1 day,
     * do not display date1 (so wee keep date1 if date2 is > date1 + 1 day)
     */
    if (date1)
    {
        if (date1->tm_year == date2->tm_year)
        {
            if (date1->tm_yday == date2->tm_yday - 1)
                date1 = NULL;
        }
        else if ((date1->tm_year == date2->tm_year - 1) && (date2->tm_yday == 0))
        {
            /* date2 is 01/01, then check if date1 is 31/12 */
            year1 = date1->tm_year + 1900;
            year1_last_yday = (((year1 % 400) == 0)
                              || (((year1 % 4) == 0) && ((year1 % 100) != 0))) ?
                365 : 364;
            if (date1->tm_yday == year1_last_yday)
                date1 = NULL;
        }
    }

    /* build the message to display */
    if (date1)
    {
        if (strftime (temp_message, sizeof (temp_message),
                      CONFIG_STRING(config_look_day_change_message_2dates),
                      date1) == 0)
            temp_message[0] = '\0';
        if (strftime (message, sizeof (message), temp_message, date2) == 0)
            message[0] = '\0';
    }
    else
    {
        if (strftime (message, sizeof (message),
                      CONFIG_STRING(config_look_day_change_message_1date),
                      date2) == 0)
            message[0] = '\0';
    }

    message_with_color = (strstr (message, "${")) ?
        eval_expression (message, NULL, NULL, NULL) : NULL;

    /* display the message */
    gui_window_coords_init_line (window, window->win_chat_cursor_y);
    gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                  GUI_COLOR_CHAT_DAY_CHANGE);
    gui_chat_clrtoeol (window);
    gui_chat_display_word_raw (window, NULL,
                               (message_with_color) ? message_with_color : message,
                               0, simulate, 0, 0);
    window->win_chat_cursor_x = window->win_chat_width;

    if (message_with_color)
        free (message_with_color);
}

/*
 * Checks if time on line is the same as time on previous line.
 *
 * Returns:
 *   1: time is same as time on previous line
 *   0: time is different from time on previous line
 */

int
gui_chat_line_time_is_same_as_previous (struct t_gui_line *line)
{
    struct t_gui_line *prev_line;

    /* previous line is not found => display standard time */
    prev_line = gui_line_get_prev_displayed (line);
    if (!prev_line)
        return 0;

    /* previous line has no time => display standard time */
    if (!line->data->str_time || !prev_line->data->str_time)
        return 0;

    /* time can be hidden/replaced if times are equal */
    return (strcmp (line->data->str_time, prev_line->data->str_time) == 0) ?
        1 : 0;
}

/*
 * Displays time, buffer name (for merged buffers) and prefix for a line.
 */

void
gui_chat_display_time_to_prefix (struct t_gui_window *window,
                                 struct t_gui_line *line,
                                 int num_lines, int count,
                                 int pre_lines_displayed,
                                 int *lines_displayed,
                                 int simulate)
{
    char str_space[] = " ";
    char *prefix_no_color, *prefix_highlighted, *ptr_prefix, *ptr_prefix2;
    char *ptr_prefix_color;
    const char *short_name, *str_color, *ptr_nick_prefix, *ptr_nick_suffix;
    int i, length, length_allowed, num_spaces, prefix_length, extra_spaces;
    int chars_displayed, nick_offline, prefix_is_nick, length_nick_prefix_suffix;
    int chars_to_display;
    struct t_gui_lines *mixed_lines;

    if (!simulate)
    {
        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].line = line;
        gui_chat_reset_style (window, line, 0, 1,
                              GUI_COLOR_CHAT_INACTIVE_WINDOW,
                              GUI_COLOR_CHAT_INACTIVE_BUFFER,
                              GUI_COLOR_CHAT);
        gui_chat_clrtoeol (window);
    }

    /* display time */
    if (window->buffer->time_for_each_line
        && line->data->str_time && line->data->str_time[0])
    {
        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].time_x1 = window->win_chat_cursor_x;

        if (CONFIG_STRING(config_look_buffer_time_same)
            && CONFIG_STRING(config_look_buffer_time_same)[0]
            && gui_chat_line_time_is_same_as_previous (line))
        {
            length_allowed = gui_chat_time_length;
            num_spaces = length_allowed - gui_chat_strlen_screen (
                config_buffer_time_same_evaluated);
            gui_chat_display_word (
                window, line, config_buffer_time_same_evaluated,
                (num_spaces < 0) ?
                config_buffer_time_same_evaluated +
                gui_chat_string_real_pos (config_buffer_time_same_evaluated,
                                          length_allowed,
                                          1) :
                NULL,
                1, num_lines, count,
                pre_lines_displayed, lines_displayed,
                simulate,
                CONFIG_BOOLEAN(config_look_color_inactive_time),
                0);
            for (i = 0; i < num_spaces; i++)
            {
                gui_chat_display_word (
                    window, line, str_space,
                    NULL, 1, num_lines, count,
                    pre_lines_displayed, lines_displayed,
                    simulate,
                    CONFIG_BOOLEAN(config_look_color_inactive_time),
                    0);
            }
        }
        else
        {
            gui_chat_display_word (
                window, line, line->data->str_time,
                NULL, 1, num_lines, count,
                pre_lines_displayed, lines_displayed,
                simulate,
                CONFIG_BOOLEAN(config_look_color_inactive_time),
                0);
        }

        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].time_x2 = window->win_chat_cursor_x - 1;

        if (!simulate)
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                  GUI_COLOR_CHAT);
        }
        gui_chat_display_word (window, line, str_space,
                               NULL, 1, num_lines, count,
                               pre_lines_displayed, lines_displayed,
                               simulate,
                               CONFIG_BOOLEAN(config_look_color_inactive_time),
                               0);
    }

    /* display buffer name (if many buffers are merged) */
    mixed_lines = line->data->buffer->mixed_lines;
    if (mixed_lines && (line->data->buffer->active != 2))
    {
        if ((CONFIG_INTEGER(config_look_prefix_buffer_align_max) > 0)
            && (CONFIG_INTEGER(config_look_prefix_buffer_align) != CONFIG_LOOK_PREFIX_BUFFER_ALIGN_NONE))
        {
            length_allowed =
                (mixed_lines->buffer_max_length <= CONFIG_INTEGER(config_look_prefix_buffer_align_max)) ?
                mixed_lines->buffer_max_length : CONFIG_INTEGER(config_look_prefix_buffer_align_max);
        }
        else
            length_allowed = mixed_lines->buffer_max_length;

        short_name = gui_buffer_get_short_name (line->data->buffer);
        length = gui_chat_strlen_screen (short_name);
        num_spaces = length_allowed - length;

        if (CONFIG_INTEGER(config_look_prefix_buffer_align) == CONFIG_LOOK_PREFIX_BUFFER_ALIGN_RIGHT)
        {
            if (!simulate)
            {
                gui_chat_reset_style (window, line, 0, 1,
                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                      GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                      GUI_COLOR_CHAT);
            }
            for (i = 0; i < num_spaces; i++)
            {
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                       0);
            }
        }

        if (!simulate)
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  (CONFIG_BOOLEAN(config_look_color_inactive_buffer)
                                   && CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer)
                                   && !line->data->buffer->active) ?
                                  GUI_COLOR_CHAT_PREFIX_BUFFER_INACTIVE_BUFFER :
                                  GUI_COLOR_CHAT_PREFIX_BUFFER,
                                  GUI_COLOR_CHAT_PREFIX_BUFFER);
        }

        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].buffer_x1 = window->win_chat_cursor_x;

        /* not enough space to display full buffer name? => truncate it! */
        if ((CONFIG_INTEGER(config_look_prefix_buffer_align) != CONFIG_LOOK_PREFIX_BUFFER_ALIGN_NONE)
            && (num_spaces < 0))
        {
            chars_to_display = length_allowed;
            /*
             * if the "+" is not displayed in the space after text, remove one
             * more char to display the "+" before the space
             */
            if (!CONFIG_BOOLEAN(config_look_prefix_buffer_align_more_after))
                chars_to_display--;
            gui_chat_display_word (window, line,
                                   short_name,
                                   short_name +
                                   gui_chat_string_real_pos (short_name,
                                                             chars_to_display,
                                                             1),
                                   1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                   0);
        }
        else
        {
            gui_chat_display_word (window, line,
                                   short_name, NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                   0);
        }

        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].buffer_x2 = window->win_chat_cursor_x - 1;

        if ((CONFIG_INTEGER(config_look_prefix_buffer_align) != CONFIG_LOOK_PREFIX_BUFFER_ALIGN_NONE)
            && (num_spaces < 0))
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_PREFIX_MORE);
            }
            gui_chat_display_word (window, line,
                                   CONFIG_STRING(config_look_prefix_buffer_align_more),
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                   0);
            if (!CONFIG_BOOLEAN(config_look_prefix_buffer_align_more_after))
            {
                if (!simulate)
                {
                    gui_chat_reset_style (window, line, 0, 1,
                                          GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                          GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                          GUI_COLOR_CHAT);
                }
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                       0);
            }
        }
        else
        {
            if (!simulate)
            {
                gui_chat_reset_style (window, line, 0, 1,
                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                      GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                      GUI_COLOR_CHAT);
            }
            if ((CONFIG_INTEGER(config_look_prefix_buffer_align) == CONFIG_LOOK_PREFIX_BUFFER_ALIGN_LEFT)
                || ((CONFIG_INTEGER(config_look_prefix_buffer_align) == CONFIG_LOOK_PREFIX_BUFFER_ALIGN_NONE)
                && (CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)))
            {
                for (i = 0; i < num_spaces; i++)
                {
                    gui_chat_display_word (window, line, str_space,
                                           NULL, 1, num_lines, count,
                                           pre_lines_displayed, lines_displayed,
                                           simulate,
                                           CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                           0);
                }
            }
            if (mixed_lines->buffer_max_length > 0)
            {
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix_buffer),
                                       0);
            }
        }
    }

    /* get prefix for display */
    gui_line_get_prefix_for_display (line, &ptr_prefix, &prefix_length,
                                     &ptr_prefix_color, &prefix_is_nick);
    if (ptr_prefix)
    {
        ptr_prefix2 = NULL;
        if (ptr_prefix_color && ptr_prefix_color[0])
        {
            str_color = gui_color_get_custom (ptr_prefix_color);
            if (str_color && str_color[0])
            {
                length = strlen (str_color) + strlen (ptr_prefix) + 1;
                ptr_prefix2 = malloc (length);
                if (ptr_prefix2)
                {
                    snprintf (ptr_prefix2, length, "%s%s",
                              str_color, ptr_prefix);
                }
            }
        }
        ptr_prefix = (ptr_prefix2) ? ptr_prefix2 : strdup (ptr_prefix);
    }

    /* get nick prefix/suffix (if prefix is a nick) */
    if (prefix_is_nick && (config_length_nick_prefix_suffix > 0))
    {
        ptr_nick_prefix = CONFIG_STRING(config_look_nick_prefix);
        ptr_nick_suffix = CONFIG_STRING(config_look_nick_suffix);
        length_nick_prefix_suffix = config_length_nick_prefix_suffix;
    }
    else
    {
        ptr_nick_prefix = NULL;
        ptr_nick_suffix = NULL;
        length_nick_prefix_suffix = 0;
    }

    /* display prefix */
    if (ptr_prefix
        && (ptr_prefix[0]
            || (CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)))
    {
        if (!simulate)
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                  GUI_COLOR_CHAT);
        }

        if (CONFIG_INTEGER(config_look_prefix_align_max) > 0)
        {
            length_allowed =
                (window->buffer->lines->prefix_max_length <= CONFIG_INTEGER(config_look_prefix_align_max)) ?
                window->buffer->lines->prefix_max_length : CONFIG_INTEGER(config_look_prefix_align_max);
        }
        else
            length_allowed = window->buffer->lines->prefix_max_length;

        /*
         * if we are not able to display at least 1 char of prefix (inside
         * prefix/suffix), then do not display nick prefix/suffix at all
         */
        if (ptr_nick_prefix && ptr_nick_suffix
            && (length_nick_prefix_suffix + 1 > length_allowed))
        {
            ptr_nick_prefix = NULL;
            ptr_nick_suffix = NULL;
            length_nick_prefix_suffix = 0;
        }

        num_spaces = length_allowed - prefix_length - length_nick_prefix_suffix;

        if (CONFIG_INTEGER(config_look_prefix_align) == CONFIG_LOOK_PREFIX_ALIGN_RIGHT)
        {
            for (i = 0; i < num_spaces; i++)
            {
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                       0);
            }
        }

        /* display prefix before nick (for example "<") */
        if (ptr_nick_prefix)
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_NICK_PREFIX);
            }
            gui_chat_display_word (window, line,
                                   ptr_nick_prefix,
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate, 0, 0);
        }

        nick_offline = CONFIG_BOOLEAN(config_look_color_nick_offline)
            && gui_line_has_offline_nick (line);

        prefix_highlighted = NULL;
        if (line->data->highlight)
        {
            prefix_no_color = gui_color_decode (ptr_prefix, NULL);
            if (prefix_no_color)
            {
                length = strlen (prefix_no_color) + 32;
                prefix_highlighted = malloc (length);
                if (prefix_highlighted)
                {
                    snprintf (prefix_highlighted, length, "%s%s",
                              GUI_COLOR(GUI_COLOR_CHAT_HIGHLIGHT),
                              prefix_no_color);
                }
                free (prefix_no_color);
            }
            if (!simulate)
            {
                gui_chat_reset_style (window, line, (nick_offline) ? 2 : 0, 1,
                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                      (CONFIG_BOOLEAN(config_look_color_inactive_buffer)
                                       && CONFIG_BOOLEAN(config_look_color_inactive_prefix)
                                       && !line->data->buffer->active) ?
                                      GUI_COLOR_CHAT_INACTIVE_BUFFER :
                                      GUI_COLOR_CHAT_HIGHLIGHT,
                                      GUI_COLOR_CHAT_HIGHLIGHT);
            }
        }
        else
        {
            if (!simulate)
            {
                gui_chat_reset_style (window, line, nick_offline, 1,
                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                      (CONFIG_BOOLEAN(config_look_color_inactive_buffer)
                                       && CONFIG_BOOLEAN(config_look_color_inactive_prefix)
                                       && !line->data->buffer->active) ?
                                      GUI_COLOR_CHAT_INACTIVE_BUFFER :
                                      GUI_COLOR_CHAT,
                                      GUI_COLOR_CHAT);
            }
        }

        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].prefix_x1 = window->win_chat_cursor_x;

        /* not enough space to display full prefix? => truncate it! */
        extra_spaces = 0;
        if ((CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)
            && (num_spaces < 0))
        {
            chars_to_display = length_allowed - length_nick_prefix_suffix;
            /*
             * if the "+" is not displayed in the space after text, remove one
             * more char to display the "+" before the space
             */
            if (!CONFIG_BOOLEAN(config_look_prefix_align_more_after))
                chars_to_display--;
            chars_displayed = gui_chat_display_word (window, line,
                                                     (prefix_highlighted) ? prefix_highlighted : ptr_prefix,
                                                     (prefix_highlighted) ?
                                                     prefix_highlighted + gui_chat_string_real_pos (prefix_highlighted,
                                                                                                    chars_to_display, 1) :
                                                     ptr_prefix + gui_chat_string_real_pos (ptr_prefix,
                                                                                            chars_to_display, 1),
                                                     1, num_lines, count,
                                                     pre_lines_displayed,
                                                     lines_displayed,
                                                     simulate,
                                                     CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                                     nick_offline);
            extra_spaces = length_allowed - length_nick_prefix_suffix - 1 - chars_displayed;
        }
        else
        {
            gui_chat_display_word (window, line,
                                   (prefix_highlighted) ? prefix_highlighted : ptr_prefix,
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                   nick_offline);
        }

        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].prefix_x2 = window->win_chat_cursor_x - 1;

        if (prefix_highlighted)
            free (prefix_highlighted);

        if (!simulate)
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                  GUI_COLOR_CHAT);
        }

        for (i = 0; i < extra_spaces; i++)
        {
            gui_chat_display_word (window, line, str_space,
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                   0);
        }

        if (!CONFIG_BOOLEAN(config_look_prefix_align_more_after)
            && (CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)
            && (num_spaces < 0))
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_PREFIX_MORE);
            }
            gui_chat_display_word (window, line,
                                   CONFIG_STRING(config_look_prefix_align_more),
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                   0);
        }

        /* display suffix after nick (for example ">") */
        if (ptr_nick_suffix)
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_NICK_SUFFIX);
            }
            gui_chat_display_word (window, line,
                                   ptr_nick_suffix,
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate, 0, 0);
        }

        if (CONFIG_INTEGER(config_look_prefix_align) == CONFIG_LOOK_PREFIX_ALIGN_LEFT)
        {
            if (!simulate)
            {
                gui_chat_reset_style (window, line, 0, 1,
                                      GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                      GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                      GUI_COLOR_CHAT);
            }
            for (i = 0; i < num_spaces; i++)
            {
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                       0);
            }
        }

        if (CONFIG_BOOLEAN(config_look_prefix_align_more_after)
            && (CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)
            && (num_spaces < 0))
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_PREFIX_MORE);
            }
            gui_chat_display_word (window, line,
                                   CONFIG_STRING(config_look_prefix_align_more),
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate,
                                   CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                   0);
        }
        else
        {
            if (window->buffer->lines->prefix_max_length > 0)
            {
                if (!simulate)
                {
                    gui_chat_reset_style (window, line, 0, 1,
                                          GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                          GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                          GUI_COLOR_CHAT);
                }
                gui_chat_display_word (window, line, str_space,
                                       NULL, 1, num_lines, count,
                                       pre_lines_displayed, lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_prefix),
                                       0);
            }
        }

        if ((CONFIG_INTEGER(config_look_prefix_align) != CONFIG_LOOK_PREFIX_ALIGN_NONE)
            && CONFIG_STRING(config_look_prefix_suffix)
            && CONFIG_STRING(config_look_prefix_suffix)[0])
        {
            if (!simulate)
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_PREFIX_SUFFIX);
            }
            gui_chat_display_word (window, line,
                                   CONFIG_STRING(config_look_prefix_suffix),
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate, 0, 0);
            gui_chat_display_word (window, line, str_space,
                                   NULL, 1, num_lines, count,
                                   pre_lines_displayed, lines_displayed,
                                   simulate, 0, 0);
        }
    }
    if (ptr_prefix)
        free (ptr_prefix);
}

/*
 * Displays a line in the chat window.
 *
 * If count == 0, display whole line.
 * If count > 0, display 'count' lines (beginning from the end).
 * If simulate == 1, nothing is displayed (for counting how many lines would
 * have been displayed).
 *
 * Returns number of lines displayed (or simulated).
 */

int
gui_chat_display_line (struct t_gui_window *window, struct t_gui_line *line,
                       int count, int simulate)
{
    int num_lines, x, y, pre_lines_displayed, lines_displayed, line_align;
    int read_marker_x, read_marker_y;
    int word_start_offset, word_end_offset;
    int word_length_with_spaces, word_length;
    char *message_with_tags, *message_with_search;
    const char *ptr_data, *ptr_end_offset, *ptr_style, *next_char;
    struct t_gui_line *ptr_prev_line, *ptr_next_line;
    struct tm local_time, local_time2;
    struct timeval tv_time;
    time_t seconds, *ptr_time;

    if (!line)
        return 0;

    if (simulate)
    {
        x = window->win_chat_cursor_x;
        y = window->win_chat_cursor_y;
        window->win_chat_cursor_x = 0;
        window->win_chat_cursor_y = 0;
        num_lines = 0;
    }
    else
    {
        if (window->win_chat_cursor_y > window->win_chat_height - 1)
            return 0;
        x = window->win_chat_cursor_x;
        y = window->win_chat_cursor_y;
        num_lines = gui_chat_display_line (window, line, 0, 1);
        window->win_chat_cursor_x = x;
        window->win_chat_cursor_y = y;
        gui_window_current_emphasis = 0;
    }

    pre_lines_displayed = 0;
    lines_displayed = 0;

    /* display message before first line of buffer if date is not today */
    if ((line->data->date != 0)
        && CONFIG_BOOLEAN(config_look_day_change)
        && window->buffer->day_change)
    {
        ptr_time = NULL;
        ptr_prev_line = gui_line_get_prev_displayed (line);
        if (ptr_prev_line)
        {
            while (ptr_prev_line && (ptr_prev_line->data->date == 0))
            {
                ptr_prev_line = gui_line_get_prev_displayed (ptr_prev_line);
            }
        }
        if (!ptr_prev_line)
        {
            gettimeofday (&tv_time, NULL);
            seconds = tv_time.tv_sec;
            localtime_r (&seconds, &local_time);
            localtime_r (&line->data->date, &local_time2);
            if ((local_time.tm_mday != local_time2.tm_mday)
                || (local_time.tm_mon != local_time2.tm_mon)
                || (local_time.tm_year != local_time2.tm_year))
            {
                gui_chat_display_day_changed (window, NULL, &local_time2,
                                              simulate);
                gui_chat_display_new_line (window, num_lines, count,
                                           &lines_displayed, simulate);
                pre_lines_displayed++;
            }
        }
    }

    /* calculate marker position (maybe not used for this line!) */
    if (window->buffer->time_for_each_line && line->data->str_time)
        read_marker_x = x + gui_chat_strlen_screen (line->data->str_time);
    else
        read_marker_x = x;
    read_marker_y = y;

    /* display time and prefix */
    gui_chat_display_time_to_prefix (window, line, num_lines, count,
                                     pre_lines_displayed, &lines_displayed,
                                     simulate);
    if (!simulate && !gui_chat_display_tags)
    {
        if (window->win_chat_cursor_y < window->coords_size)
            window->coords[window->win_chat_cursor_y].data = line->data->message;
        window->coords_x_message = window->win_chat_cursor_x;
    }

    /* reset color & style for a new line */
    if (!simulate)
    {
        if (CONFIG_BOOLEAN(config_look_color_inactive_message))
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                  GUI_COLOR_CHAT);
        }
        else
        {
            gui_chat_reset_style (window, line, 0, 1,
                                  GUI_COLOR_CHAT,
                                  GUI_COLOR_CHAT,
                                  GUI_COLOR_CHAT);
        }
    }

    /* display message */
    ptr_data = NULL;
    message_with_tags = NULL;
    message_with_search = NULL;

    if (line->data->message && line->data->message[0])
    {
        message_with_tags = (gui_chat_display_tags) ?
            gui_chat_build_string_message_tags (line) : NULL;
        ptr_data = (message_with_tags) ?
            message_with_tags : line->data->message;
        message_with_search = NULL;
        if ((window->buffer->text_search != GUI_TEXT_SEARCH_DISABLED)
            && (window->buffer->text_search_where & GUI_TEXT_SEARCH_IN_MESSAGE)
            && (!window->buffer->text_search_regex
                || window->buffer->text_search_regex_compiled))
        {
            message_with_search = gui_color_emphasize (ptr_data,
                                                       window->buffer->input_buffer,
                                                       window->buffer->text_search_exact,
                                                       window->buffer->text_search_regex_compiled);
            if (message_with_search)
                ptr_data = message_with_search;
        }
    }

    if (ptr_data && ptr_data[0])
    {
        while (ptr_data && ptr_data[0])
        {
            gui_chat_get_word_info (window,
                                    ptr_data,
                                    &word_start_offset,
                                    &word_end_offset,
                                    &word_length_with_spaces, &word_length);

            ptr_end_offset = ptr_data + word_end_offset;

            /* if message ends with spaces, display them */
            if ((word_length <= 0) && (word_length_with_spaces > 0)
                && !ptr_data[word_end_offset + 1])
            {
                word_length = word_length_with_spaces;
            }

            if (word_length >= 0)
            {
                line_align = gui_line_get_align (window->buffer, line, 1,
                                                 (lines_displayed == 0) ? 1 : 0);
                if ((window->win_chat_cursor_x + word_length_with_spaces > gui_chat_get_real_width (window))
                    && (word_length <= gui_chat_get_real_width (window) - line_align))
                {
                    /* spaces + word too long for current line but OK for next line */
                    gui_chat_display_new_line (window, num_lines, count,
                                               &lines_displayed, simulate);
                    /* apply styles before jumping to start of word */
                    if (!simulate && (word_start_offset > 0))
                    {
                        ptr_style = ptr_data;
                        while (ptr_style < ptr_data + word_start_offset)
                        {
                            /* loop until no style/char available */
                            ptr_style = gui_chat_string_next_char (window, line,
                                                                   (unsigned char *)ptr_style,
                                                                   1,
                                                                   CONFIG_BOOLEAN(config_look_color_inactive_message),
                                                                   0);
                            if (!ptr_style)
                                break;
                            ptr_style = utf8_next_char (ptr_style);
                        }
                    }
                    /* jump to start of word */
                    ptr_data += word_start_offset;
                }

                /* display word */
                gui_chat_display_word (window, line, ptr_data,
                                       ptr_end_offset + 1,
                                       0, num_lines, count,
                                       pre_lines_displayed, &lines_displayed,
                                       simulate,
                                       CONFIG_BOOLEAN(config_look_color_inactive_message),
                                       0);

                if ((!simulate) && (window->win_chat_cursor_y >= window->win_chat_height))
                    ptr_data = NULL;
                else
                {
                    /* move pointer after end of word */
                    ptr_data = ptr_end_offset + 1;
                    if (*(ptr_data - 1) == '\0')
                        ptr_data = NULL;

                    if (window->win_chat_cursor_x == 0)
                    {
                        while (ptr_data && (ptr_data[0] == ' '))
                        {
                            next_char = utf8_next_char (ptr_data);
                            if (!next_char)
                                break;
                            ptr_data = gui_chat_string_next_char (window, line,
                                                                  (unsigned char *)next_char,
                                                                  1,
                                                                  CONFIG_BOOLEAN(config_look_color_inactive_message),
                                                                  0);
                        }
                    }
                }
            }
            else
            {
                gui_chat_display_new_line (window, num_lines, count,
                                           &lines_displayed, simulate);
                ptr_data = NULL;
            }
        }
    }
    else
    {
        /* no message */
        gui_chat_display_new_line (window, num_lines, count,
                                   &lines_displayed, simulate);
    }

    if (message_with_tags)
        free (message_with_tags);
    if (message_with_search)
        free (message_with_search);

    /* display message if day has changed after this line */
    if ((line->data->date != 0)
        && CONFIG_BOOLEAN(config_look_day_change)
        && window->buffer->day_change)
    {
        ptr_time = NULL;
        ptr_next_line = gui_line_get_next_displayed (line);
        if (ptr_next_line)
        {
            while (ptr_next_line && (ptr_next_line->data->date == 0))
            {
                ptr_next_line = gui_line_get_next_displayed (ptr_next_line);
            }
        }
        if (ptr_next_line)
        {
            /* get time of next line */
            ptr_time = &ptr_next_line->data->date;
        }
        else
        {
            /* it was the last line => compare with current system time */
            gettimeofday (&tv_time, NULL);
            seconds = tv_time.tv_sec;
            ptr_time = &seconds;
        }
        if (ptr_time && (*ptr_time != 0))
        {
            localtime_r (&line->data->date, &local_time);
            localtime_r (ptr_time, &local_time2);
            if ((local_time.tm_mday != local_time2.tm_mday)
                || (local_time.tm_mon != local_time2.tm_mon)
                || (local_time.tm_year != local_time2.tm_year))
            {
                gui_chat_display_day_changed (window, &local_time, &local_time2,
                                              simulate);
                gui_chat_display_new_line (window, num_lines, count,
                                           &lines_displayed, simulate);
            }
        }
    }

    /* display read marker (after line) */
    if (gui_chat_marker_for_line (window->buffer, line))
    {
        gui_chat_display_horizontal_line (window, simulate);
        gui_chat_display_new_line (window, num_lines, count,
                                   &lines_displayed, simulate);
    }

    if (simulate)
    {
        window->win_chat_cursor_x = x;
        window->win_chat_cursor_y = y;
    }
    else
    {
        /* display marker if line is matching user search */
        if (window->buffer->text_search != GUI_TEXT_SEARCH_DISABLED)
        {
            if (gui_line_search_text (window->buffer, line))
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_TEXT_FOUND);
                mvwaddstr (GUI_WINDOW_OBJECTS(window)->win_chat,
                           read_marker_y, read_marker_x,
                           "*");
            }
        }
        else
        {
            /* display read marker if needed */
            if ((CONFIG_INTEGER(config_look_read_marker) == CONFIG_LOOK_READ_MARKER_CHAR)
                && window->buffer->lines->last_read_line
                && (window->buffer->lines->last_read_line == gui_line_get_prev_displayed (line)))
            {
                gui_window_set_weechat_color (GUI_WINDOW_OBJECTS(window)->win_chat,
                                              GUI_COLOR_CHAT_READ_MARKER);
                mvwaddstr (GUI_WINDOW_OBJECTS(window)->win_chat,
                           read_marker_y, read_marker_x,
                           "*");
            }
        }
    }

    return lines_displayed;
}

/*
 * Displays a line in the chat window (for a buffer with free content).
 */

void
gui_chat_display_line_y (struct t_gui_window *window, struct t_gui_line *line,
                         int y)
{
    char *ptr_data, *message_with_search;

    /* reset color & style for a new line */
    gui_chat_reset_style (window, line, 0, 1,
                          GUI_COLOR_CHAT_INACTIVE_WINDOW,
                          GUI_COLOR_CHAT_INACTIVE_BUFFER,
                          GUI_COLOR_CHAT);

    window->win_chat_cursor_x = 0;
    window->win_chat_cursor_y = y;
    gui_window_current_emphasis = 0;

    gui_chat_clrtoeol (window);

    /* emphasize text (if searching text) */
    ptr_data = line->data->message;
    message_with_search = NULL;
    if ((window->buffer->text_search != GUI_TEXT_SEARCH_DISABLED)
        && (window->buffer->text_search_where & GUI_TEXT_SEARCH_IN_MESSAGE)
        && (!window->buffer->text_search_regex
            || window->buffer->text_search_regex_compiled))
    {
        message_with_search = gui_color_emphasize (ptr_data,
                                                   window->buffer->input_buffer,
                                                   window->buffer->text_search_exact,
                                                   window->buffer->text_search_regex_compiled);
        if (message_with_search)
            ptr_data = message_with_search;
    }

    /* display the line */
    if (gui_chat_display_word_raw (window, line, ptr_data,
                                   window->win_chat_width, 0,
                                   CONFIG_BOOLEAN(config_look_color_inactive_message),
                                   0) < window->win_chat_width)
    {
        gui_window_clrtoeol (GUI_WINDOW_OBJECTS(window)->win_chat);
    }

    if (message_with_search)
        free (message_with_search);
}

/*
 * Returns pointer to line & offset for a difference with given line.
 */

void
gui_chat_calculate_line_diff (struct t_gui_window *window,
                              struct t_gui_line **line, int *line_pos,
                              int difference)
{
    int backward, current_size;

    if (!line || !line_pos)
        return;

    backward = (difference < 0);

    if (*line && (*line_pos < 0))
    {
        *line = (*line)->next_line;
        *line_pos = 0;
    }

    if (!(*line))
    {
        /* if looking backward, start at last line of buffer */
        if (backward)
        {
            *line = gui_line_get_last_displayed (window->buffer);
            if (!(*line))
                return;
            current_size = gui_chat_display_line (window, *line, 0, 1);
            if (current_size == 0)
                current_size = 1;
            *line_pos = current_size - 1;
        }
        /* if looking forward, start at first line of buffer */
        else
        {
            *line = gui_line_get_first_displayed (window->buffer);
            if (!(*line))
                return;
            *line_pos = 0;
            current_size = gui_chat_display_line (window, *line, 0, 1);
        }
    }
    else
        current_size = gui_chat_display_line (window, *line, 0, 1);

    while ((*line) && (difference != 0))
    {
        /* looking backward */
        if (backward)
        {
            if (*line_pos > 0)
                (*line_pos)--;
            else
            {
                *line = gui_line_get_prev_displayed (*line);
                if (*line)
                {
                    current_size = gui_chat_display_line (window, *line, 0, 1);
                    if (current_size == 0)
                        current_size = 1;
                    *line_pos = current_size - 1;
                }
            }
            difference++;
        }
        /* looking forward */
        else
        {
            if (*line_pos < current_size - 1)
                (*line_pos)++;
            else
            {
                *line = gui_line_get_next_displayed (*line);
                if (*line)
                {
                    current_size = gui_chat_display_line (window, *line, 0, 1);
                    if (current_size == 0)
                        current_size = 1;
                    *line_pos = 0;
                }
            }
            difference--;
        }
    }

    /* first or last line reached */
    if (!(*line))
    {
        if (backward)
        {
            /* first line reached */
            *line = gui_line_get_first_displayed (window->buffer);
            *line_pos = 0;
        }
        else
        {
            /* last line reached => consider we'll display all until the end */
            *line_pos = 0;
        }
    }
}

/*
 * Draws chat window for a formatted buffer.
 */

void
gui_chat_draw_formatted_buffer (struct t_gui_window *window)
{
    struct t_gui_line *ptr_line, *ptr_line2;
    int auto_search_first_line, line_pos, line_pos2, count;
    int old_scrolling, old_lines_after;

    /* display at position of scrolling */
    auto_search_first_line = 1;
    ptr_line = NULL;
    line_pos = 0;
    if (window->scroll->start_line)
    {
        auto_search_first_line = 0;
        ptr_line = window->scroll->start_line;
        line_pos = window->scroll->start_line_pos;
        if (line_pos < 0)
        {
            ptr_line = gui_line_get_next_displayed (ptr_line);
            line_pos = 0;
            if (ptr_line)
            {
                ptr_line2 = ptr_line;
                line_pos2 = 0;
                gui_chat_calculate_line_diff (window, &ptr_line2, &line_pos2,
                                              window->win_chat_height);
                if (ptr_line2)
                {
                    auto_search_first_line = 1;
                    window->scroll->start_line = NULL;
                    window->scroll->start_line_pos = 0;
                    ptr_line = NULL;
                    line_pos = 0;
                }
            }
        }
        else if (!ptr_line->data->displayed)
        {
            /* skip filtered lines on top when scrolling */
            ptr_line = gui_line_get_next_displayed (ptr_line);
            line_pos = 0;
        }
    }

    if (auto_search_first_line)
    {
        /* look for first line to display, starting from last line */
        gui_chat_calculate_line_diff (window, &ptr_line, &line_pos,
                                      (-1) * (window->win_chat_height - 1));
    }

    if (!ptr_line)
        return;

    count = 0;

    if (line_pos > 0)
    {
        /* display end of first line at top of screen */
        count = gui_chat_display_line (window, ptr_line,
                                       gui_chat_display_line (window,
                                                              ptr_line,
                                                              0, 1) -
                                       line_pos, 0);
        ptr_line = gui_line_get_next_displayed (ptr_line);
        window->scroll->first_line_displayed = 0;
    }
    else
        window->scroll->first_line_displayed =
            (ptr_line == gui_line_get_first_displayed (window->buffer));

    /* display lines */
    while (ptr_line && (window->win_chat_cursor_y <= window->win_chat_height - 1))
    {
        count = gui_chat_display_line (window, ptr_line, 0, 0);
        ptr_line = gui_line_get_next_displayed (ptr_line);
    }

    old_scrolling = window->scroll->scrolling;
    old_lines_after = window->scroll->lines_after;

    window->scroll->scrolling = (window->win_chat_cursor_y > window->win_chat_height - 1);

    /* check if last line of buffer is entirely displayed and scrolling */
    /* if so, disable scroll indicator */
    if (!ptr_line && window->scroll->scrolling)
    {
        if ((count == gui_chat_display_line (window, gui_line_get_last_displayed (window->buffer), 0, 1))
            || (count == window->win_chat_height))
            window->scroll->scrolling = 0;
    }

    if (!window->scroll->scrolling
        && (window->scroll->start_line == gui_line_get_first_displayed (window->buffer)))
    {
        window->scroll->start_line = NULL;
        window->scroll->start_line_pos = 0;
    }

    window->scroll->lines_after = 0;
    if (window->scroll->scrolling && ptr_line)
    {
        /* count number of lines after last line displayed */
        while (ptr_line)
        {
            ptr_line = gui_line_get_next_displayed (ptr_line);
            if (ptr_line)
                window->scroll->lines_after++;
        }
        window->scroll->lines_after++;
    }

    if ((window->scroll->scrolling != old_scrolling)
        || (window->scroll->lines_after != old_lines_after))
    {
        (void) hook_signal_send ("window_scrolled",
                                 WEECHAT_HOOK_SIGNAL_POINTER, window);
    }

    /* cursor is below end line of chat window? */
    if (window->win_chat_cursor_y > window->win_chat_height - 1)
    {
        window->win_chat_cursor_x = 0;
        window->win_chat_cursor_y = window->win_chat_height - 1;
    }
}

/*
 * Draws chat window for a free buffer.
 */

void
gui_chat_draw_free_buffer (struct t_gui_window *window, int clear_chat)
{
    struct t_gui_line *ptr_line;
    int y_start, y_end, y;

    ptr_line = NULL;
    if (window->scroll->start_line)
    {
        ptr_line = window->scroll->start_line;
        if (window->scroll->start_line_pos < 0)
            ptr_line = gui_line_get_next_displayed (ptr_line);
    }
    else
        ptr_line = window->buffer->lines->first_line;

    if (ptr_line)
    {
        if (!ptr_line->data->displayed)
            ptr_line = gui_line_get_next_displayed (ptr_line);
        if (ptr_line)
        {
            y_start = (window->scroll->start_line) ? ptr_line->data->y : 0;
            y_end = y_start + window->win_chat_height - 1;
            while (ptr_line && (ptr_line->data->y <= y_end))
            {
                y = ptr_line->data->y - y_start;
                if (y < window->coords_size)
                {
                    window->coords[y].line = ptr_line;
                    window->coords[y].data = ptr_line->data->message;
                }
                if (ptr_line->data->refresh_needed || clear_chat)
                {
                    gui_chat_display_line_y (window, ptr_line,
                                             y);
                }
                ptr_line = gui_line_get_next_displayed (ptr_line);
            }
        }
    }
}

/*
 * Gets line content in bare display.
 *
 * Note: result must be freed after use.
 */

char *
gui_chat_get_bare_line (struct t_gui_line *line)
{
    char *prefix, *message, str_time[256], *str_line;
    const char *tag_prefix_nick;
    struct tm *local_time;
    int length;

    prefix = NULL;
    message = NULL;
    str_line = NULL;

    prefix = (line->data->prefix) ?
        gui_color_decode (line->data->prefix, NULL) : strdup ("");
    if (!prefix)
        goto end;
    message = (line->data->message) ?
        gui_color_decode (line->data->message, NULL) : strdup ("");
    if (!message)
        goto end;

    str_time[0] = '\0';
    if (line->data->buffer->time_for_each_line
        && (line->data->date > 0)
        && CONFIG_STRING(config_look_bare_display_time_format)
        && CONFIG_STRING(config_look_bare_display_time_format)[0])
    {
        local_time = localtime (&line->data->date);
        if (strftime (str_time, sizeof (str_time),
                      CONFIG_STRING(config_look_bare_display_time_format),
                      local_time) == 0)
            str_time[0] = '\0';
    }
    tag_prefix_nick = gui_line_search_tag_starting_with (line, "prefix_nick_");

    length = strlen (str_time) + 1 + 1 + strlen (prefix) + 1 + 1
        + strlen (message) + 1;
    str_line = malloc (length);
    if (str_line)
    {
        snprintf (str_line, length,
                  "%s%s%s%s%s%s%s",
                  str_time,
                  (str_time[0]) ? " " : "",
                  (prefix[0] && tag_prefix_nick) ? "<" : "",
                  prefix,
                  (prefix[0] && tag_prefix_nick) ? ">" : "",
                  (prefix[0]) ? " " : "",
                  message);
    }

end:
    if (prefix)
        free (prefix);
    if (message)
        free (message);

    return str_line;
}

/*
 * Draws a buffer in bare display (not ncurses).
 */

void
gui_chat_draw_bare (struct t_gui_window *window)
{
    struct t_gui_line *ptr_line;
    char *line;
    int y, length, num_lines;

    /* in bare display, we display ONLY the current window/buffer */
    if (window != gui_current_window)
        return;

    /* clear screen */
    printf ("\033[2J");

    /* display lines */
    if ((window->buffer->type == GUI_BUFFER_TYPE_FREE)
        || window->scroll->start_line)
    {
        /* display from top to bottom (starting with "start_line") */
        y = 0;
        ptr_line = (window->scroll->start_line) ?
            window->scroll->start_line : gui_line_get_first_displayed (window->buffer);
        while (ptr_line && (y < gui_term_lines))
        {
            line = gui_chat_get_bare_line (ptr_line);
            if (!line)
                break;
            length = utf8_strlen_screen (line);
            num_lines = length / gui_term_cols;
            if (length % gui_term_cols != 0)
                num_lines++;
            if (y + num_lines <= gui_term_lines)
                printf ("\033[%d;1H%s", y + 1, line);
            free (line);
            y += num_lines;
            ptr_line = gui_line_get_next_displayed (ptr_line);
        }
    }
    else
    {
        /* display from bottom to top (starting with last line of buffer) */
        y = gui_term_lines;
        ptr_line = gui_line_get_last_displayed (window->buffer);
        while (ptr_line && (y >= 0))
        {
            line = gui_chat_get_bare_line (ptr_line);
            if (!line)
                break;
            length = utf8_strlen_screen (line);
            num_lines = length / gui_term_cols;
            if (length % gui_term_cols != 0)
                num_lines++;
            y -= num_lines;
            if (y >= 0)
                printf ("\033[%d;1H%s", y + 1, line);
            free (line);
            ptr_line = gui_line_get_prev_displayed (ptr_line);
        }
    }

    /*
     * move cursor to top/left or bottom/right, according to type of buffer and
     * whether we are scrolling or not
     */
    printf ("\033[%d;1H",
            (window->buffer->type == GUI_BUFFER_TYPE_FREE) ?
            ((window->scroll->start_line) ? gui_term_lines : 1) :
            ((window->scroll->start_line) ? 1 : gui_term_lines));

    fflush (stdout);
}

/*
 * Draws chat window for a buffer.
 */

void
gui_chat_draw (struct t_gui_buffer *buffer, int clear_chat)
{
    struct t_gui_window *ptr_win;
    struct t_gui_line *ptr_line;
    char format_empty[32];
    int i;

    if (!gui_init_ok)
        return;

    if (gui_window_bare_display)
    {
        if (gui_current_window && (gui_current_window->buffer == buffer))
            gui_chat_draw_bare (gui_current_window);
        goto end;
    }

    for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
    {
        if ((ptr_win->buffer->number == buffer->number)
            && (ptr_win->win_chat_x >= 0) && (ptr_win->win_chat_y >= 0)
            && (GUI_WINDOW_OBJECTS(ptr_win)->win_chat))
        {
            gui_window_coords_alloc (ptr_win);

            gui_chat_reset_style (ptr_win, NULL, 0, 1,
                                  GUI_COLOR_CHAT_INACTIVE_WINDOW,
                                  GUI_COLOR_CHAT_INACTIVE_BUFFER,
                                  GUI_COLOR_CHAT);

            if (clear_chat)
            {
                snprintf (format_empty, sizeof (format_empty),
                          "%%-%ds", ptr_win->win_chat_width);
                for (i = 0; i < ptr_win->win_chat_height; i++)
                {
                    mvwprintw (GUI_WINDOW_OBJECTS(ptr_win)->win_chat, i, 0,
                               format_empty, " ");
                }
            }

            ptr_win->win_chat_cursor_x = 0;
            ptr_win->win_chat_cursor_y = 0;

            switch (ptr_win->buffer->type)
            {
                case GUI_BUFFER_TYPE_FORMATTED:
                    /* min 2 lines for chat area */
                    if (ptr_win->win_chat_height < 2)
                        mvwaddstr (GUI_WINDOW_OBJECTS(ptr_win)->win_chat, 0, 0, "...");
                    else
                        gui_chat_draw_formatted_buffer (ptr_win);
                    break;
                case GUI_BUFFER_TYPE_FREE:
                    gui_chat_draw_free_buffer (ptr_win, clear_chat);
                    break;
                case GUI_BUFFER_NUM_TYPES:
                    break;
            }
            wnoutrefresh (GUI_WINDOW_OBJECTS(ptr_win)->win_chat);
        }
    }

    refresh ();

    if (buffer->type == GUI_BUFFER_TYPE_FREE)
    {
        for (ptr_line = buffer->lines->first_line; ptr_line;
             ptr_line = ptr_line->next_line)
        {
            ptr_line->data->refresh_needed = 0;
        }
    }

end:
    buffer->chat_refresh_needed = 0;
}
