/*
 * Copyright (c) 2003-2008 by FlashCode <flashcode@flashtux.org>
 * See README for License detail, AUTHORS for developers list.
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* gui-curses-bar.c: bar functions for Curses GUI */


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

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>

#include "../../core/weechat.h"
#include "../../core/wee-config.h"
#include "../../core/wee-log.h"
#include "../../core/wee-string.h"
#include "../../core/wee-utf8.h"
#include "../gui-bar.h"
#include "../gui-bar-item.h"
#include "../gui-chat.h"
#include "../gui-color.h"
#include "../gui-window.h"
#include "gui-curses.h"


/*
 * gui_bar_window_search_bar: search a reference to a bar in a window
 */

struct t_gui_bar_window *
gui_bar_window_search_bar (struct t_gui_window *window, struct t_gui_bar *bar)
{
    struct t_gui_bar_window *ptr_bar_win;

    for (ptr_bar_win = GUI_CURSES(window)->bar_windows; ptr_bar_win;
         ptr_bar_win = ptr_bar_win->next_bar_window)
    {
        if (ptr_bar_win->bar == bar)
            return ptr_bar_win;
    }
    
    /* bar window not found for window */
    return NULL;
}

/*
 * gui_bar_window_get_size: get total bar size (window bars) for a position
 *                          bar is optional, if not NULL, size is computed
 *                          from bar 1 to bar # - 1
 */

int
gui_bar_window_get_size (struct t_gui_bar *bar, struct t_gui_window *window,
                         enum t_gui_bar_position position)
{
    struct t_gui_bar_window *ptr_bar_window;
    int total_size;
    
    total_size = 0;
    for (ptr_bar_window = GUI_CURSES(window)->bar_windows; ptr_bar_window;
         ptr_bar_window = ptr_bar_window->next_bar_window)
    {
        /* stop before bar */
        if (bar && (ptr_bar_window->bar == bar))
            return total_size;

        if (!CONFIG_BOOLEAN(ptr_bar_window->bar->hidden))
        {
            if ((CONFIG_INTEGER(ptr_bar_window->bar->type) != GUI_BAR_TYPE_ROOT)
                && (CONFIG_INTEGER(ptr_bar_window->bar->position) == (int)position))
            {
                switch (position)
                {
                    case GUI_BAR_POSITION_BOTTOM:
                    case GUI_BAR_POSITION_TOP:
                        total_size += ptr_bar_window->height;
                        break;
                    case GUI_BAR_POSITION_LEFT:
                    case GUI_BAR_POSITION_RIGHT:
                        total_size += ptr_bar_window->width;
                        break;
                    case GUI_BAR_NUM_POSITIONS:
                        break;
                }
                if (CONFIG_INTEGER(ptr_bar_window->bar->separator))
                    total_size++;
            }
        }
    }
    return total_size;
}

/*
 * gui_bar_get_min_width: return minimum width of a bar window displayed for
 *                        a bar
 *                        for example, if a bar is displayed in 3 windows,
 *                        this function return min width of these 3 bar windows
 */

int
gui_bar_get_min_width (struct t_gui_bar *bar)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;
    int min_width;
    
    if (CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
        return bar->bar_window->width;
    
    min_width = INT_MAX;
    for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
    {
        for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
             ptr_bar_win; ptr_bar_win = ptr_bar_win->next_bar_window)
        {
            if (ptr_bar_win->bar == bar)
            {
                if (ptr_bar_win->width < min_width)
                    min_width = ptr_bar_win->width;
            }
        }
    }
    
    if (min_width == INT_MAX)
        return 0;
    
    return min_width;
}

/*
 * gui_bar_get_min_height: return minimum height of a bar window displayed for
 *                         a bar
 *                         for example, if a bar is displayed in 3 windows,
 *                         this function return min width of these 3 bar windows
 */

int
gui_bar_get_min_height (struct t_gui_bar *bar)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;
    int min_height;
    
    if (CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
        return bar->bar_window->height;
    
    min_height = INT_MAX;
    for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
    {
        for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
             ptr_bar_win; ptr_bar_win = ptr_bar_win->next_bar_window)
        {
            if (ptr_bar_win->bar == bar)
            {
                if (ptr_bar_win->height < min_height)
                    min_height = ptr_bar_win->height;
            }
        }
    }
    
    if (min_height == INT_MAX)
        return 0;
    
    return min_height;
}

/*
 * gui_bar_check_size_add: check if "add_size" is ok for bar
 *                         return 1 if new size is ok
 *                                0 if new size is too big
 */

int
gui_bar_check_size_add (struct t_gui_bar *bar, int add_size)
{
    struct t_gui_window *ptr_win;
    int sub_width, sub_height;
    
    sub_width = 0;
    sub_height = 0;
    
    switch (CONFIG_INTEGER(bar->position))
    {
        case GUI_BAR_POSITION_BOTTOM:
        case GUI_BAR_POSITION_TOP:
            sub_height = add_size;
            break;
        case GUI_BAR_POSITION_LEFT:
        case GUI_BAR_POSITION_RIGHT:
            sub_width = add_size;
            break;
        case GUI_BAR_NUM_POSITIONS:
            break;
    }
    
    for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
    {
        if ((CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
            || (gui_bar_window_search_bar (ptr_win, bar)))
        {
            if ((ptr_win->win_chat_width - sub_width < GUI_WINDOW_CHAT_MIN_WIDTH)
                || (ptr_win->win_chat_height - sub_height < GUI_WINDOW_CHAT_MIN_HEIGHT))
                return 0;
        }
    }
    
    /* new size ok */
    return 1;
}

/*
 * gui_bar_window_calculate_pos_size: calculate position and size of a bar
 */

void
gui_bar_window_calculate_pos_size (struct t_gui_bar_window *bar_window,
                                   struct t_gui_window *window)
{
    int x1, y1, x2, y2;
    int add_bottom, add_top, add_left, add_right;
    
    if (window)
    {
        x1 = window->win_x;
        y1 = window->win_y;
        x2 = x1 + window->win_width - 1;
        y2 = y1 + window->win_height - 1;
        add_bottom = gui_bar_window_get_size (bar_window->bar, window, GUI_BAR_POSITION_BOTTOM);
        add_top = gui_bar_window_get_size (bar_window->bar, window, GUI_BAR_POSITION_TOP);
        add_left = gui_bar_window_get_size (bar_window->bar, window, GUI_BAR_POSITION_LEFT);
        add_right = gui_bar_window_get_size (bar_window->bar, window, GUI_BAR_POSITION_RIGHT);
    }
    else
    {
        x1 = 0;
        y1 = 0;
        x2 = gui_window_get_width () - 1;
        y2 = gui_window_get_height () - 1;
        add_bottom = gui_bar_root_get_size (bar_window->bar, GUI_BAR_POSITION_BOTTOM);
        add_top = gui_bar_root_get_size (bar_window->bar, GUI_BAR_POSITION_TOP);
        add_left = gui_bar_root_get_size (bar_window->bar, GUI_BAR_POSITION_LEFT);
        add_right = gui_bar_root_get_size (bar_window->bar, GUI_BAR_POSITION_RIGHT);
    }
    
    switch (CONFIG_INTEGER(bar_window->bar->position))
    {
        case GUI_BAR_POSITION_BOTTOM:
            bar_window->x = x1 + add_left;
            bar_window->y = y2 - add_bottom - bar_window->current_size + 1;
            bar_window->width = x2 - x1 + 1 - add_left - add_right;
            bar_window->height = bar_window->current_size;
            break;
        case GUI_BAR_POSITION_TOP:
            bar_window->x = x1 + add_left;
            bar_window->y = y1 + add_top;
            bar_window->width = x2 - x1 + 1 - add_left - add_right;
            bar_window->height = bar_window->current_size;
            break;
        case GUI_BAR_POSITION_LEFT:
            bar_window->x = x1 + add_left;
            bar_window->y = y1 + add_top;
            bar_window->width = bar_window->current_size;
            bar_window->height = y2 - y1 + 1 - add_top - add_bottom;
            break;
        case GUI_BAR_POSITION_RIGHT:
            bar_window->x = x2 - add_right - bar_window->current_size + 1;
            bar_window->y = y1 + add_top;
            bar_window->width = bar_window->current_size;
            bar_window->height = y2 - y1 + 1 - add_top - add_bottom;
            break;
        case GUI_BAR_NUM_POSITIONS:
            break;
    }
}

/*
 * gui_bar_window_create_win: create curses window for bar
 */

void
gui_bar_window_create_win (struct t_gui_bar_window *bar_window)
{
    if (bar_window->win_bar)
    {
        delwin (bar_window->win_bar);
        bar_window->win_bar = NULL;
    }
    if (bar_window->win_separator)
    {
        delwin (bar_window->win_separator);
        bar_window->win_separator = NULL;
    }
    
    bar_window->win_bar = newwin (bar_window->height,
                                  bar_window->width,
                                  bar_window->y,
                                  bar_window->x);
    
    if (CONFIG_INTEGER(bar_window->bar->separator))
    {
        switch (CONFIG_INTEGER(bar_window->bar->position))
        {
            case GUI_BAR_POSITION_BOTTOM:
                bar_window->win_separator = newwin (1,
                                                    bar_window->width,
                                                    bar_window->y - 1,
                                                    bar_window->x);
                break;
            case GUI_BAR_POSITION_TOP:
                bar_window->win_separator = newwin (1,
                                                    bar_window->width,
                                                    bar_window->y + bar_window->height,
                                                    bar_window->x);
                break;
            case GUI_BAR_POSITION_LEFT:
                bar_window->win_separator = newwin (bar_window->height,
                                                    1,
                                                    bar_window->y,
                                                    bar_window->x + bar_window->width);
                break;
            case GUI_BAR_POSITION_RIGHT:
                bar_window->win_separator = newwin (bar_window->height,
                                                    1,
                                                    bar_window->y,
                                                    bar_window->x - 1);
                break;
            case GUI_BAR_NUM_POSITIONS:
                break;
        }
    }
}

/*
 * gui_bar_window_find_pos: find position for bar window (keeping list sorted
 *                          by bar priority)
 */

struct t_gui_bar_window *
gui_bar_window_find_pos (struct t_gui_bar *bar, struct t_gui_window *window)
{
    struct t_gui_bar_window *ptr_bar_window;
    
    for (ptr_bar_window = GUI_CURSES(window)->bar_windows; ptr_bar_window;
         ptr_bar_window = ptr_bar_window->next_bar_window)
    {
        if (CONFIG_INTEGER(bar->priority) >= CONFIG_INTEGER(ptr_bar_window->bar->priority))
            return ptr_bar_window;
    }
    
    /* position not found, best position is at the end */
    return NULL;
}

/*
 * gui_bar_window_new: create a new "window bar" for a bar, in screen or a window
 *                     if window is not NULL, bar window will be in this window
 *                     return 1 if ok, 0 if error
 */

int
gui_bar_window_new (struct t_gui_bar *bar, struct t_gui_window *window)
{
    struct t_gui_bar_window *new_bar_window, *pos_bar_window;
    
    if (window)
    {
        if ((CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_WINDOW)
            && (!gui_bar_check_conditions_for_window (bar, window)))
            return 1;
    }
    
    new_bar_window =  malloc (sizeof (*new_bar_window));
    if (new_bar_window)
    {
        new_bar_window->bar = bar;
        if (window)
        {
            bar->bar_window = NULL;
            if (GUI_CURSES(window)->bar_windows)
            {
                pos_bar_window = gui_bar_window_find_pos (bar, window);
                if (pos_bar_window)
                {
                    /* insert before bar window found */
                    new_bar_window->prev_bar_window = pos_bar_window->prev_bar_window;
                    new_bar_window->next_bar_window = pos_bar_window;
                    if (pos_bar_window->prev_bar_window)
                        (pos_bar_window->prev_bar_window)->next_bar_window = new_bar_window;
                    else
                        GUI_CURSES(window)->bar_windows = new_bar_window;
                    pos_bar_window->prev_bar_window = new_bar_window;
                }
                else
                {
                    /* add to end of list for window */
                    new_bar_window->prev_bar_window = GUI_CURSES(window)->last_bar_window;
                    new_bar_window->next_bar_window = NULL;
                    (GUI_CURSES(window)->last_bar_window)->next_bar_window = new_bar_window;
                    GUI_CURSES(window)->last_bar_window = new_bar_window;
                }
            }
            else
            {
                new_bar_window->prev_bar_window = NULL;
                new_bar_window->next_bar_window = NULL;
                GUI_CURSES(window)->bar_windows = new_bar_window;
                GUI_CURSES(window)->last_bar_window = new_bar_window;
            }
        }
        else
        {
            bar->bar_window = new_bar_window;
            new_bar_window->prev_bar_window = NULL;
            new_bar_window->next_bar_window = NULL;
        }
        new_bar_window->win_bar = NULL;
        new_bar_window->win_separator = NULL;
        
        new_bar_window->x = 0;
        new_bar_window->y = 0;
        new_bar_window->width = 1;
        new_bar_window->height = 1;
        new_bar_window->scroll_x = 0;
        new_bar_window->scroll_y = 0;
        new_bar_window->cursor_x = -1;
        new_bar_window->cursor_y = -1;
        new_bar_window->current_size = (CONFIG_INTEGER(bar->size) == 0) ?
            1 : CONFIG_INTEGER(bar->size);
        
        if (gui_init_ok)
        {
            gui_bar_window_calculate_pos_size (new_bar_window, window);
            gui_bar_window_create_win (new_bar_window);
            if (window)
                window->refresh_needed = 1;
        }
        
        return 1;
    }
    
    /* failed to create bar window */
    return 0;
}

/*
 * gui_bar_window_recreate_bar_windows: recreate bar windows for all windows
 */

void
gui_bar_window_recreate_bar_windows (struct t_gui_bar *bar)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;

    if (CONFIG_BOOLEAN(bar->hidden))
        return;
    
    if (CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
    {
        gui_bar_window_calculate_pos_size (bar->bar_window, NULL);
        gui_bar_window_create_win (bar->bar_window);
        gui_window_refresh_needed = 1;
    }
    else
    {
        for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
        {
            for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
                 ptr_bar_win; ptr_bar_win = ptr_bar_win->next_bar_window)
            {
                if (ptr_bar_win->bar == bar)
                {
                    gui_bar_window_calculate_pos_size (ptr_bar_win, ptr_win);
                    gui_bar_window_create_win (ptr_bar_win);
                    ptr_win->refresh_needed = 1;
                }
            }
        }
    }
}

/*
 * gui_bar_window_get_current_size: get current size of bar window
 *                                  return width or height, depending on bar
 *                                  position
 */

int
gui_bar_window_get_current_size (struct t_gui_bar_window *bar_window)
{
    return bar_window->current_size;
}

/*
 * gui_bar_window_set_current_size: set current size of all bar windows for a bar
 */

void
gui_bar_window_set_current_size (struct t_gui_bar *bar, int size)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;
    int new_size;
    
    if (size == 0)
        new_size = 1;
    else
    {
        new_size = size;
        if ((size != 0) && (CONFIG_INTEGER(bar->size_max) > 0)
            && (size > CONFIG_INTEGER(bar->size_max)))
        {
            new_size = CONFIG_INTEGER(bar->size_max);
            if (new_size < 1)
                new_size = 1;
        }
    }
    
    if (CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
    {
        if (bar->bar_window->current_size != new_size)
        {
            bar->bar_window->current_size = new_size;
            gui_bar_window_recreate_bar_windows (bar);
        }
    }
    else
    {
        for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
        {
            for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
                 ptr_bar_win; ptr_bar_win = ptr_bar_win->next_bar_window)
            {
                if ((ptr_bar_win->bar == bar)
                    && (ptr_bar_win->current_size != new_size))
                {
                    ptr_bar_win->current_size = new_size;
                    gui_bar_window_recreate_bar_windows (bar);
                }
            }
        }
    }
}

/*
 * gui_bar_window_free: free a bar window
 */

void
gui_bar_window_free (struct t_gui_bar_window *bar_window,
                     struct t_gui_window *window)
{
    /* remove window bar from list */
    if (window)
    {
        if (bar_window->prev_bar_window)
            (bar_window->prev_bar_window)->next_bar_window = bar_window->next_bar_window;
        if (bar_window->next_bar_window)
            (bar_window->next_bar_window)->prev_bar_window = bar_window->prev_bar_window;
        if (GUI_CURSES(window)->bar_windows == bar_window)
            GUI_CURSES(window)->bar_windows = bar_window->next_bar_window;
        if (GUI_CURSES(window)->last_bar_window == bar_window)
            GUI_CURSES(window)->last_bar_window = bar_window->prev_bar_window;
        
        window->refresh_needed = 1;
    }
    
    /* free data */
    if (bar_window->win_bar)
        delwin (bar_window->win_bar);
    if (bar_window->win_separator)
        delwin (bar_window->win_separator);
    
    free (bar_window);
}

/*
 * gui_bar_free_bar_windows: free bar windows for a bar
 */

void
gui_bar_free_bar_windows (struct t_gui_bar *bar)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win, *next_bar_win;
    
    for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
    {
        ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
        while (ptr_bar_win)
        {
            next_bar_win = ptr_bar_win->next_bar_window;
            
            if (ptr_bar_win->bar == bar)
                gui_bar_window_free (ptr_bar_win, ptr_win);
            
            ptr_bar_win = next_bar_win;
        }
    }
}

/*
 * gui_bar_window_remove_unused_bars: remove unused bars for a window
 *                                    return 1 if at least one bar was removed
 *                                           0 if no bar was removed
 */

int
gui_bar_window_remove_unused_bars (struct t_gui_window *window)
{
    int rc;
    struct t_gui_bar_window *ptr_bar_win, *next_bar_win;
    
    rc = 0;

    ptr_bar_win = GUI_CURSES(window)->bar_windows;
    while (ptr_bar_win)
    {
        next_bar_win = ptr_bar_win->next_bar_window;
        
        if ((CONFIG_INTEGER(ptr_bar_win->bar->type) == GUI_BAR_TYPE_WINDOW)
            && (!gui_bar_check_conditions_for_window (ptr_bar_win->bar, window)))
        {
            gui_bar_window_free (ptr_bar_win, window);
            rc = 1;
        }
        
        ptr_bar_win = next_bar_win;
    }
    
    return rc;
}

/*
 * gui_bar_window_add_missing_bars: add missing bars for a window
 *                                  return 1 if at least one bar was created
 *                                         0 if no bar was created
 */

int
gui_bar_window_add_missing_bars (struct t_gui_window *window)
{
    int rc;
    struct t_gui_bar *ptr_bar;
    
    rc = 0;
    
    for (ptr_bar = gui_bars; ptr_bar; ptr_bar = ptr_bar->next_bar)
    {
        if ((CONFIG_INTEGER(ptr_bar->type) == GUI_BAR_TYPE_WINDOW)
            && gui_bar_check_conditions_for_window (ptr_bar, window))
        {
            if (!gui_bar_window_search_bar (window, ptr_bar))
            {
                gui_bar_window_new (ptr_bar, window);
                rc = 1;
            }
        }
    }
    
    return rc;
}

/*
 * gui_bar_window_print_string: print a string text on a bar window
 *                              return 1 if all was printed, 0 if some text
 *                              was not displayed (wrapped due to bar window
 *                              width)
 */

int
gui_bar_window_print_string (struct t_gui_bar_window *bar_window,
                             int *x, int *y,
                             const char *string,
                             int reset_color_before_display)
{
    int weechat_color, x_with_hidden, size_on_screen, fg, bg, low_char;
    char str_fg[3], str_bg[3], utf_char[16], *next_char, *output;
    
    if (!string || !string[0])
        return 1;
    
    wmove (bar_window->win_bar, *y, *x);

    if (reset_color_before_display)
    {
        gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                           CONFIG_COLOR(bar_window->bar->color_fg),
                                           CONFIG_COLOR(bar_window->bar->color_bg));
    }
    
    x_with_hidden = *x;
    
    while (string && string[0])
    {
        if (string[0] == GUI_COLOR_COLOR_CHAR)
        {
            string++;
            switch (string[0])
            {
                case GUI_COLOR_FG_CHAR: /* fg color */
                    if (string[1] && string[2])
                    {
                        str_fg[0] = string[1];
                        str_fg[1] = string[2];
                        str_fg[2] = '\0';
                        sscanf (str_fg, "%d", &fg);
                        gui_window_set_custom_color_fg (bar_window->win_bar,
                                                        fg);
                        string += 3;
                    }
                    break;
                case GUI_COLOR_BG_CHAR: /* bg color */
                    if (string[1] && string[2])
                    {
                        str_bg[0] = string[1];
                        str_bg[1] = string[2];
                        str_bg[2] = '\0';
                        sscanf (str_bg, "%d", &bg);
                        gui_window_set_custom_color_bg (bar_window->win_bar,
                                                        bg);
                        string += 3;
                    }
                    break;
                case GUI_COLOR_FG_BG_CHAR: /* fg + bg color */
                    if (string[1] && string[2] && (string[3] == ',')
                        && string[4] && string[5])
                    {
                        str_fg[0] = string[1];
                        str_fg[1] = string[2];
                        str_fg[2] = '\0';
                        str_bg[0] = string[4];
                        str_bg[1] = string[5];
                        str_bg[2] = '\0';
                        sscanf (str_fg, "%d", &fg);
                        sscanf (str_bg, "%d", &bg);
                        gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                                           fg, bg);
                        string += 6;
                    }
                    break;
                case GUI_COLOR_BAR_CHAR: /* bar color */
                    switch (string[1])
                    {
                        case GUI_COLOR_BAR_FG_CHAR:
                            /* bar foreground */
                            gui_window_set_custom_color_fg (bar_window->win_bar,
                                                            CONFIG_INTEGER(bar_window->bar->color_fg));
                            string += 2;
                            break;
                        case GUI_COLOR_BAR_DELIM_CHAR:
                            /* bar delimiter */
                            gui_window_set_custom_color_fg (bar_window->win_bar,
                                                            CONFIG_INTEGER(bar_window->bar->color_delim));
                            string += 2;
                            break;
                        case GUI_COLOR_BAR_BG_CHAR:
                            /* bar background */
                            gui_window_set_custom_color_bg (bar_window->win_bar,
                                                            CONFIG_INTEGER(bar_window->bar->color_bg));
                            string += 2;
                            break;
                        case GUI_COLOR_BAR_MOVE_CURSOR_CHAR:
                            /* move cursor to current position on screen */
                            getyx (bar_window->win_bar,
                                   bar_window->cursor_y, bar_window->cursor_x);
                            bar_window->cursor_x += bar_window->x;
                            bar_window->cursor_y += bar_window->y;
                            string += 2;
                            break;
                        default:
                            string++;
                            break;
                    }
                    break;
                default:
                    if (isdigit (string[0]) && isdigit (string[1]))
                    {
                        str_fg[0] = string[0];
                        str_fg[1] = string[1];
                        str_fg[2] = '\0';
                        sscanf (str_fg, "%d", &weechat_color);
                        gui_window_set_weechat_color (bar_window->win_bar,
                                                      weechat_color);
                        string += 2;
                    }
                    break;
            }
        }
        else
        {
            next_char = utf8_next_char (string);
            if (!next_char)
                break;
            
            memcpy (utf_char, string, next_char - string);
            utf_char[next_char - string] = '\0';
            
            if ((((unsigned char)utf_char[0]) < 32) && (!utf_char[1]))
            {
                low_char = 1;
                snprintf (utf_char, sizeof (utf_char), "%c",
                          'A' + ((unsigned char)utf_char[0]) - 1);
            }
            else
            {
                low_char = 0;
                if (!gui_window_utf_char_valid (utf_char))
                    snprintf (utf_char, sizeof (utf_char), ".");
            }
            
            size_on_screen = utf8_char_size_screen (utf_char);
            if (size_on_screen > 0)
            {
                if (x_with_hidden < bar_window->scroll_x)
                {
                    /* hidden char (before scroll_x value) */
                    x_with_hidden++;
                }
                else
                {
                    if (*x + size_on_screen > bar_window->width)
                    {
                        if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_VERTICAL)
                            return 0;
                        if (*y >= bar_window->height - 1)
                            return 0;
                        *x = 0;
                        (*y)++;
                        wmove (bar_window->win_bar, *y, *x);
                    }
                    
                    output = string_iconv_from_internal (NULL, utf_char);
                    if (low_char)
                        wattron (bar_window->win_bar, A_REVERSE);
                    wprintw (bar_window->win_bar, "%s",
                             (output) ? output : utf_char);
                    if (low_char)
                        wattroff (bar_window->win_bar, A_REVERSE);
                    if (output)
                        free (output);
                    
                    *x += size_on_screen;
                }
            }
            
            string = next_char;
        }
    }
    return 1;
}

/*
 * gui_bar_window_draw: draw a bar for a window
 */

void
gui_bar_window_draw (struct t_gui_bar_window *bar_window,
                     struct t_gui_window *window)
{
    int x, y, i, items_count, num_lines, line;
    char *content, *item_value, *item_value2, **items;
    char space_with_reinit_color[32];
    int length_reinit_color, content_length, length, length_on_screen;
    int max_length, optimal_number_of_lines, chars_available;
    int some_data_not_displayed;
    
    if (!gui_init_ok)
        return;
    
    snprintf (space_with_reinit_color,
              sizeof (space_with_reinit_color),
              "%c%c%02d,%02d ",
              GUI_COLOR_COLOR_CHAR,
              GUI_COLOR_FG_BG_CHAR,
              CONFIG_COLOR(bar_window->bar->color_fg),
              CONFIG_COLOR(bar_window->bar->color_bg));
    length_reinit_color = strlen (space_with_reinit_color);
    
    /* these values will be overwritten later (by gui_bar_window_print_string)
       if cursor has to move somewhere in bar window */
    bar_window->cursor_x = -1;
    bar_window->cursor_y = -1;
    
    if (CONFIG_INTEGER(bar_window->bar->size) == 0)
    {
        content = NULL;
        content_length = 1;
        for (i = 0; i < bar_window->bar->items_count; i++)
        {
            item_value = gui_bar_item_get_value (bar_window->bar->items_array[i],
                                                 bar_window->bar, window,
                                                 0, 0, 0);
            if (item_value)
            {
                if (item_value[0])
                {
                    if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_HORIZONTAL)
                    {
                        item_value2 = string_replace (item_value, "\n",
                                                      space_with_reinit_color);
                    }
                    else
                        item_value2 = NULL;
                    if (!content)
                    {
                        content_length += strlen ((item_value2) ?
                                                  item_value2 : item_value);
                        content = strdup ((item_value2) ?
                                          item_value2 : item_value);
                    }
                    else
                    {
                        content_length += length_reinit_color +
                            strlen ((item_value2) ? item_value2 : item_value);
                        content = realloc (content, content_length);
                        if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_HORIZONTAL)
                            strcat (content, space_with_reinit_color);
                        else
                            strcat (content, "\n");
                        strcat (content,
                                (item_value2) ? item_value2 : item_value);
                    }
                    if (item_value2)
                        free (item_value2);
                }
                free (item_value);
            }
        }
        if (content)
        {
            items = string_explode (content, "\n", 0, 0, &items_count);
            if (items_count == 0)
            {
                gui_bar_window_set_current_size (bar_window->bar, 1);
                gui_window_clear (bar_window->win_bar,
                                  CONFIG_COLOR(bar_window->bar->color_bg));
            }
            else
            {
                /* search longer line and optimal number of lines */
                max_length = 0;
                optimal_number_of_lines = 0;
                for (line = 0; line < items_count; line++)
                {
                    length_on_screen = gui_chat_strlen_screen (items[line]);
                    
                    length = strlen (items[line]);
                    if ((length >= 3)
                        && (items[line][length - 3] == GUI_COLOR_COLOR_CHAR)
                        && (items[line][length - 2] == GUI_COLOR_BAR_CHAR)
                        && (items[line][length - 1] == GUI_COLOR_BAR_MOVE_CURSOR_CHAR))
                        length_on_screen++;
                    
                    if (length_on_screen > max_length)
                        max_length = length_on_screen;
                    
                    if (length_on_screen % bar_window->width == 0)
                        num_lines = length_on_screen / bar_window->width;
                    else
                        num_lines = (length_on_screen / bar_window->width) + 1;
                    if (num_lines == 0)
                        num_lines = 1;
                    optimal_number_of_lines += num_lines;
                }
                if (max_length == 0)
                    max_length = 1;
                
                switch (CONFIG_INTEGER(bar_window->bar->position))
                {
                    case GUI_BAR_POSITION_BOTTOM:
                    case GUI_BAR_POSITION_TOP:
                        if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_HORIZONTAL)
                            num_lines = optimal_number_of_lines;
                        else
                            num_lines = items_count;
                        gui_bar_window_set_current_size (bar_window->bar,
                                                         num_lines);
                        break;
                    case GUI_BAR_POSITION_LEFT:
                    case GUI_BAR_POSITION_RIGHT:
                        gui_bar_window_set_current_size (bar_window->bar,
                                                         max_length);
                        break;
                    case GUI_BAR_NUM_POSITIONS:
                        break;
                }
                gui_window_clear (bar_window->win_bar,
                                  CONFIG_COLOR(bar_window->bar->color_bg));
                x = 0;
                y = 0;
                some_data_not_displayed = 0;
                if ((bar_window->scroll_y > 0)
                    && (bar_window->scroll_y >= items_count))
                {
                    bar_window->scroll_y = items_count - bar_window->height;
                    if (bar_window->scroll_y < 0)
                        bar_window->scroll_y = 0;
                }
                for (line = 0;
                     (line < items_count) && (y < bar_window->height);
                     line++)
                {
                    if ((bar_window->scroll_y == 0)
                        || (line >= bar_window->scroll_y))
                    {
                        if (!gui_bar_window_print_string (bar_window, &x, &y,
                                                          items[line], 1))
                        {
                            some_data_not_displayed = 1;
                        }
                        if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_VERTICAL)
                        {
                            while (x < bar_window->width)
                            {
                                gui_bar_window_print_string (bar_window,
                                                             &x, &y,
                                                             " ", 0);
                            }
                            x = 0;
                            y++;
                        }
                        else
                        {
                            gui_bar_window_print_string (bar_window, &x, &y,
                                                         space_with_reinit_color, 0);
                        }
                    }
                }
                if ((bar_window->cursor_x < 0) && (bar_window->cursor_y < 0)
                    && ((bar_window->scroll_x > 0) || (bar_window->scroll_y > 0)))
                {
                    x = (bar_window->height > 1) ? bar_window->width - 2 : 0;
                    if (x < 0)
                        x = 0;
                    y = 0;
                    gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                                       CONFIG_COLOR(config_color_bar_more),
                                                       CONFIG_INTEGER(bar_window->bar->color_bg));
                    mvwprintw (bar_window->win_bar, y, x, "--");
                }
                if ((bar_window->cursor_x < 0) && (bar_window->cursor_y < 0)
                    && (some_data_not_displayed || (line < items_count)))
                {
                    x = bar_window->width - 2;
                    if (x < 0)
                        x = 0;
                    y = (bar_window->height > 1) ? bar_window->height - 1 : 0;
                    gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                                       CONFIG_COLOR(config_color_bar_more),
                                                       CONFIG_INTEGER(bar_window->bar->color_bg));
                    mvwprintw (bar_window->win_bar, y, x, "++");
                }
            }
            if (items)
                string_free_exploded (items);
            free (content);
        }
        else
        {
            gui_bar_window_set_current_size (bar_window->bar, 1);
            gui_window_clear (bar_window->win_bar,
                              CONFIG_COLOR(bar_window->bar->color_bg));
        }
    }
    else
    {
        gui_window_clear (bar_window->win_bar,
                          CONFIG_COLOR(bar_window->bar->color_bg));
        
        x = 0;
        y = 0;
        
        for (i = 0; i < bar_window->bar->items_count; i++)
        {
            chars_available =
                ((bar_window->height - y - 1) * bar_window->width) + /* next lines */
                (bar_window->width - x - 1); /* chars on current line */
            
            item_value = gui_bar_item_get_value (bar_window->bar->items_array[i],
                                                 bar_window->bar, window,
                                                 bar_window->width,
                                                 bar_window->height,
                                                 chars_available);
            if (item_value)
            {
                if (item_value[0])
                {
                    if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_HORIZONTAL)
                    {
                        item_value2 = string_replace (item_value, "\n",
                                                      space_with_reinit_color);
                    }
                    else
                        item_value2 = NULL;
                    items = string_explode ((item_value2) ?
                                            item_value2 : item_value,
                                            "\n", 0, 0,
                                            &items_count);
                    some_data_not_displayed = 0;
                    if ((bar_window->scroll_y > 0)
                        && (bar_window->scroll_y >= items_count))
                    {
                        bar_window->scroll_y = items_count - bar_window->height;
                        if (bar_window->scroll_y < 0)
                            bar_window->scroll_y = 0;
                    }
                    for (line = 0;
                         (line < items_count) && (y < bar_window->height);
                         line++)
                    {
                        if ((bar_window->scroll_y == 0)
                            || (line >= bar_window->scroll_y))
                        {
                            if (!gui_bar_window_print_string (bar_window, &x, &y,
                                                              items[line], 1))
                            {
                                some_data_not_displayed = 1;
                            }
                            if (CONFIG_INTEGER(gui_bar_get_option_filling (bar_window->bar)) == GUI_BAR_FILLING_VERTICAL)
                            {
                                while (x < bar_window->width)
                                {
                                    gui_bar_window_print_string (bar_window,
                                                                 &x, &y,
                                                                 " ", 0);
                                }
                                x = 0;
                                y++;
                            }
                            else
                            {
                                gui_bar_window_print_string (bar_window, &x, &y,
                                                             space_with_reinit_color, 0);
                            }
                        }
                    }
                    if ((bar_window->cursor_x < 0) && (bar_window->cursor_y < 0)
                        && ((bar_window->scroll_x > 0) || (bar_window->scroll_y > 0)))
                    {
                        x = (bar_window->height > 1) ? bar_window->width - 2 : 0;
                        if (x < 0)
                            x = 0;
                        y = 0;
                        gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                                           CONFIG_COLOR(config_color_bar_more),
                                                           CONFIG_INTEGER(bar_window->bar->color_bg));
                        mvwprintw (bar_window->win_bar, y, x, "--");
                    }
                    if ((bar_window->cursor_x < 0) && (bar_window->cursor_y < 0)
                        && (some_data_not_displayed || (line < items_count)))
                    {
                        x = bar_window->width - 2;
                        if (x < 0)
                            x = 0;
                        y = (bar_window->height > 1) ? bar_window->height - 1 : 0;
                        gui_window_set_custom_color_fg_bg (bar_window->win_bar,
                                                           CONFIG_COLOR(config_color_bar_more),
                                                           CONFIG_INTEGER(bar_window->bar->color_bg));
                        mvwprintw (bar_window->win_bar, y, x, "++");
                    }
                    if (item_value2)
                        free (item_value2);
                    if (items)
                        string_free_exploded (items);
                }
                free (item_value);
            }
        }
    }

    /* move cursor if it was asked in an item content (input_text does that
       to move cursor in user input text) */
    if (window && (gui_current_window == window)
        && (bar_window->cursor_x >= 0) && (bar_window->cursor_y >= 0))
    {
        move (bar_window->cursor_y, bar_window->cursor_x);
    }
    
    wnoutrefresh (bar_window->win_bar);
    
    if (CONFIG_INTEGER(bar_window->bar->separator))
    {
        switch (CONFIG_INTEGER(bar_window->bar->position))
        {
            case GUI_BAR_POSITION_BOTTOM:
                gui_window_set_weechat_color (bar_window->win_separator,
                                              GUI_COLOR_SEPARATOR);
                mvwhline (bar_window->win_separator, 0, 0, ACS_HLINE,
                          bar_window->width);
                break;
            case GUI_BAR_POSITION_TOP:
                gui_window_set_weechat_color (bar_window->win_separator,
                                              GUI_COLOR_SEPARATOR);
                mvwhline (bar_window->win_separator, 0, 0, ACS_HLINE,
                          bar_window->width);
                break;
            case GUI_BAR_POSITION_LEFT:
                gui_window_set_weechat_color (bar_window->win_separator,
                                              GUI_COLOR_SEPARATOR);
                mvwvline (bar_window->win_separator, 0, 0, ACS_VLINE,
                          bar_window->height);
                break;
            case GUI_BAR_POSITION_RIGHT:
                gui_window_set_weechat_color (bar_window->win_separator,
                                              GUI_COLOR_SEPARATOR);
                mvwvline (bar_window->win_separator, 0, 0, ACS_VLINE,
                          bar_window->height);
                break;
            case GUI_BAR_NUM_POSITIONS:
                break;
        }
        wnoutrefresh (bar_window->win_separator);
    }
    
    refresh ();
}

/*
 * gui_bar_draw: draw a bar
 */

void
gui_bar_draw (struct t_gui_bar *bar)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;
    
    if (CONFIG_BOOLEAN(bar->hidden))
        return;
    
    if (bar->bar_window)
    {
        /* root bar */
        gui_bar_window_draw (bar->bar_window, NULL);
    }
    else
    {
        /* bar on each window */
        for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
        {
            for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows; ptr_bar_win;
                 ptr_bar_win = ptr_bar_win->next_bar_window)
            {
                if (ptr_bar_win->bar == bar)
                {
                    gui_bar_window_draw (ptr_bar_win, ptr_win);
                }
            }
        }
    }
    
    bar->bar_refresh_needed = 0;
}

/*
 * gui_bar_window_scroll: scroll a bar window with a value
 *                        if add == 1, then value is added (otherwise subtracted)
 *                        if add_x == 1, then value is added to scroll_x (otherwise scroll_y)
 *                        if percent == 1, then value is a percentage (otherwise number of chars)
 */

void
gui_bar_window_scroll (struct t_gui_bar_window *bar_window,
                       struct t_gui_window *window,
                       int add_x, int scroll_beginning, int scroll_end,
                       int add, int percent, int value)
{
    int old_scroll_x, old_scroll_y;
    
    old_scroll_x = bar_window->scroll_x;
    old_scroll_y = bar_window->scroll_y;

    if (scroll_beginning)
    {
        if (add_x)
            bar_window->scroll_x = 0;
        else
            bar_window->scroll_y = 0;
    }
    else if (scroll_end)
    {
        if (add_x)
            bar_window->scroll_x = INT_MAX;
        else
            bar_window->scroll_y = INT_MAX;
    }
    else
    {
        if (percent)
        {
            if (add_x)
                value = (bar_window->width * value) / 100;
            else
                value = (bar_window->height * value) / 100;
            if (value == 0)
                value = 1;
        }
        if (add)
        {
            if (add_x)
                bar_window->scroll_x += value;
            else
                bar_window->scroll_y += value;
        }
        else
        {
            if (add_x)
                bar_window->scroll_x -= value;
            else
                bar_window->scroll_y -= value;
        }
    }
    
    if (bar_window->scroll_x < 0)
        bar_window->scroll_x = 0;
    
    if (bar_window->scroll_y < 0)
        bar_window->scroll_y = 0;
    
    /* refresh only if scroll has changed (X and/or Y) */
    if ((old_scroll_x != bar_window->scroll_x)
        || (old_scroll_y != bar_window->scroll_y))
    {
        gui_bar_window_draw (bar_window, window);
    }
}

/*
 * gui_bar_scroll: scroll a bar for a buffer
 *                 return 1 if scroll is ok, 0 if error
 */

int
gui_bar_scroll (struct t_gui_bar *bar, struct t_gui_buffer *buffer,
                const char *scroll)
{
    struct t_gui_window *ptr_win;
    struct t_gui_bar_window *ptr_bar_win;
    long number;
    char *str, *error;
    int length, add_x, add, percent, scroll_beginning, scroll_end;
    
    add_x = 0;
    str = NULL;
    number = 0;
    add = 0;
    percent = 0;
    scroll_beginning = 0;
    scroll_end = 0;
    
    if ((scroll[0] == 'x') || (scroll[0] == 'X'))
    {
        add_x = 1;
        scroll++;
    }
    else if ((scroll[0] == 'y') || (scroll[0] == 'Y'))
    {
        scroll++;
    }
    else
        return 0;
    
    if ((scroll[0] == 'b') || (scroll[0] == 'B'))
    {
        scroll_beginning = 1;
    }
    else if ((scroll[0] == 'e') || (scroll[0] == 'E'))
    {
        scroll_end = 1;
    }
    else
    {
        if (scroll[0] == '+')
        {
            add = 1;
            scroll++;
        }
        else if (scroll[0] == '-')
        {
            scroll++;
        }
        else
            return 0;
        
        length = strlen (scroll);
        if (length == 0)
            return 0;
    
        if (scroll[length - 1] == '%')
        {
            str = string_strndup (scroll, length - 1);
            percent = 1;
        }
        else
            str = strdup (scroll);
        if (!str)
            return 0;
        
        error = NULL;
        number = strtol (str, &error, 10);
        
        if (!error || error[0] || (number <= 0))
        {
            free (str);
            return 0;
        }
    }
    
    if (CONFIG_INTEGER(bar->type) == GUI_BAR_TYPE_ROOT)
        gui_bar_window_scroll (bar->bar_window, NULL,
                               add_x, scroll_beginning, scroll_end,
                               add, percent, number);
    else
    {
        for (ptr_win = gui_windows; ptr_win; ptr_win = ptr_win->next_window)
        {
            if (ptr_win->buffer == buffer)
            {
                for (ptr_bar_win = GUI_CURSES(ptr_win)->bar_windows;
                     ptr_bar_win; ptr_bar_win = ptr_bar_win->next_bar_window)
                {
                    if (ptr_bar_win->bar == bar)
                    {
                        gui_bar_window_scroll (ptr_bar_win, ptr_win,
                                               add_x, scroll_beginning, scroll_end,
                                               add, percent, number);
                    }
                }
            }
        }
    }
    
    free (str);
    
    return 1;
}

/*
 * gui_bar_window_print_log: print bar window infos in log (usually for crash dump)
 */

void
gui_bar_window_print_log (struct t_gui_bar_window *bar_window)
{
    log_printf ("");
    log_printf ("  [window bar (addr:0x%lx)]",   bar_window);
    log_printf ("    bar . . . . . . . : 0x%lx ('%s')",
                bar_window->bar,
                (bar_window->bar) ? bar_window->bar->name : "");
    log_printf ("    x . . . . . . . . : %d",    bar_window->x);
    log_printf ("    y . . . . . . . . : %d",    bar_window->y);
    log_printf ("    width . . . . . . : %d",    bar_window->width);
    log_printf ("    height. . . . . . : %d",    bar_window->height);
    log_printf ("    scroll_x. . . . . : %d",    bar_window->scroll_x);
    log_printf ("    scroll_y. . . . . : %d",    bar_window->scroll_y);
    log_printf ("    cursor_x. . . . . : %d",    bar_window->cursor_x);
    log_printf ("    cursor_y. . . . . : %d",    bar_window->cursor_y);
    log_printf ("    current_size. . . : %d",    bar_window->current_size);
    log_printf ("    win_bar . . . . . : 0x%lx", bar_window->win_bar);
    log_printf ("    win_separator . . : 0x%lx", bar_window->win_separator);
    log_printf ("    prev_bar_window . : 0x%lx", bar_window->prev_bar_window);
    log_printf ("    next_bar_window . : 0x%lx", bar_window->next_bar_window);
}
