/*
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * See the COPYING file for license information.
 *
 * Guillaume Chazarain <guichaz@yahoo.fr>
 */

/*************************
 * The top level widgets *
 *************************/

#include <string.h>             /* memcpy() */
#include <gdk/gdkx.h>           /* gdk_net_wm_supports() */

#include "gliv.h"
#include "windows.h"
#include "options.h"
#include "gliv-image.h"
#include "callbacks.h"
#include "messages.h"
#include "math_floats.h"
#include "str_utils.h"
#include "matrix.h"
#include "scrollbars.h"
#include "gl_widget.h"
#include "menus.h"
#include "cursors.h"

extern rt_struct *rt;
extern options_struct *options;
extern GlivImage *current_image;
extern GtkWidget *gl_widget;
extern GtkMenuBar *menu_bar;

/* What is in the window. */
static GtkVBox *widgets;

static GtkWindow *main_window;
static GtkWindow *fs_window;

/* The main status bar and its subdivisions. */
static GtkHBox *status_bar;
static GtkEntry *entry_ident;
static GtkEntry *entry_size;
static GtkEntry *entry_state;

/*
 * Called when resizing the window because the image changed or a
 * widget has been toggled.
 */
static void resize_window(gint width, gint height, gboolean force)
{
    if (options->resize_win == FALSE && force == FALSE)
        return;

    if (width < 0)
        width = rt->wid_size->width;

    if (height < 0)
        height = rt->wid_size->height;

    width = MAX(width, 320);
    height = MAX(height, 240);

    /*
     * First, we let GTK resize the window around the
     * width x height widget,
     */
    gtk_widget_set_size_request(gl_widget, width, height);
    gtk_window_resize(main_window, 1, 1);

    process_events();

    /* then, we make the widget downsizable. */
    gtk_widget_set_size_request(gl_widget, 1, 1);
}

void show_message(GtkWidget * widget, const gchar * name)
{
    /* In windowed mode we let the WM choose the position. */
    if (options->fullscreen)
        gtk_window_set_position(GTK_WINDOW(widget), GTK_WIN_POS_MOUSE);

    if (name != NULL)
        gtk_window_set_title(GTK_WINDOW(widget), name);

    gtk_widget_show_all(widget);
}

gint run_modal_dialog(GtkDialog * dialog)
{
    gint response;

    gtk_window_set_transient_for(GTK_WINDOW(dialog), get_current_window());
    gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);

    /* In windowed mode we let the WM choose the position. */
    if (options->fullscreen)
        gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);

    /* We want the cursor to be visible when the dialog is shown. */
    set_hide_cursor_enabled(FALSE);
    response = gtk_dialog_run(GTK_DIALOG(dialog));
    set_hide_cursor_enabled(TRUE);

    return response;
}

static gint get_entry_border(GtkEntry * entry)
{
    gint interior_focus, focus_width;
    gint border;

    gtk_widget_style_get(GTK_WIDGET(entry),
                         "interior-focus", &interior_focus,
                         "focus-line-width", &focus_width, NULL);

    border = entry->has_frame ? GTK_WIDGET(entry)->style->xthickness : 0;

    if (!interior_focus)
        border += focus_width;

    return border + 2;          /* + 2 : INNER_BORDER */
}

static gint status_bar_free_space(void)
{
    gint space = 0;
    GtkEntry *entries[] = { entry_ident, entry_size, entry_state, NULL };
    GtkEntry **ptr;

    for (ptr = entries; *ptr != NULL; ptr++) {
        GtkRequisition req;

        gtk_widget_size_request(GTK_WIDGET(*ptr), &req);
        space += GTK_WIDGET(*ptr)->allocation.width - req.width;
    }

    return space;
}

static void set_entry_text(GtkEntry * entry, const gchar * text)
{
    GtkRequisition old;
    GtkRequisition req;
    gboolean accept_resize;

    gtk_entry_set_text(entry, text);

    /* Compute the full width */
    gtk_widget_size_request(GTK_WIDGET(entry), &old);
    memcpy(&req, &old, sizeof(GtkRequisition));
    pango_layout_get_pixel_size(gtk_entry_get_layout(entry), &req.width, NULL);
    req.width += 2 * get_entry_border(entry);

    if (req.width > old.width) {
        gint free_space = status_bar_free_space();

        accept_resize = req.width - old.width <= free_space;
    } else
        accept_resize = old.width - req.width > 20;

    if (accept_resize)
        gtk_widget_set_size_request(GTK_WIDGET(entry), req.width, req.height);
}

static gint ident_basename_pos(const gchar * ident)
{
    gint pos;
    gint last1 = 0, last2 = 0;

    for (pos = 0; *ident != '\0'; ident = g_utf8_next_char(ident)) {
        if (g_utf8_get_char(ident) == '/') {
            last2 = last1;
            last1 = pos;
        }
        pos++;
    }

    return last2;
}

static void set_ident_text(const gchar * text)
{
    gint pos;

    pos = ident_basename_pos(text);

    gtk_entry_set_text(entry_ident, text);
    gtk_editable_set_position(GTK_EDITABLE(entry_ident), pos);
}

/* To add an entry in the status bar. */
static GtkEntry *add_entry(gboolean grow, const gchar * init)
{
    GtkEntry *entry;

    entry = GTK_ENTRY(gtk_entry_new());
    gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
    gtk_box_pack_start(GTK_BOX(status_bar), GTK_WIDGET(entry), grow, TRUE, 0);

    /* We don't want the blinking cursor. */
    GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(entry), GTK_CAN_FOCUS);

    set_entry_text(entry, init);

    return entry;
}

static void create_status_bar(void)
{
    status_bar = GTK_HBOX(gtk_hbox_new(FALSE, 0));

    entry_ident = add_entry(TRUE, _("No image loaded"));
    entry_size = add_entry(FALSE, _("width x height"));
    entry_state = add_entry(FALSE, _("zoom% (angle)"));
}

void set_loading_entry(const gchar * filename)
{
    static gchar *old_ident = NULL;

    if (filename == NULL) {
        /* Revert to the previous text without the loading information. */
        set_ident_text(old_ident);
        g_free(old_ident);
    } else {
        /* Add a loading information. */
        gchar *new_ident;

        old_ident = gtk_editable_get_chars(GTK_EDITABLE(entry_ident), 0, -1);

        new_ident = g_strconcat(old_ident, " ", _("loading"), ":",
                                filename_to_utf8(filename), NULL);

        set_ident_text(new_ident);
        g_free(new_ident);
    }

    if (options->status_bar) {
        /* Make sure the widget is updated now. */
        gtk_widget_queue_draw(GTK_WIDGET(status_bar));
        gdk_window_process_updates(GTK_WIDGET(status_bar)->window, TRUE);
    }
}

/* Returns TRUE if the widgets has been toggled. */
gboolean toggle_widgets(GtkWidget ** widgets, gint nb_widgets, gboolean * flag)
{
    /* We are not reentrant. */
    static gboolean toggling = FALSE;
    gint w, h;
    gint i;

    if (toggling)
        return FALSE;

    toggling = TRUE;
    process_events();
    w = rt->wid_size->width;
    h = rt->wid_size->height;

    for (i = 0; i < nb_widgets; i++) {
        if (*flag == FALSE)
            /* Show. */
            gtk_widget_show(widgets[i]);
        else
            /* Hide. */
            gtk_widget_hide(widgets[i]);
    }

    *flag ^= TRUE;

    if (options->fullscreen == FALSE)
        resize_window(w, h, TRUE);

    process_events();
    toggling = FALSE;
    return TRUE;
}

static gboolean toggle_widget(GtkWidget * widget, gboolean * flag)
{
    return toggle_widgets(&widget, 1, flag);
}

gboolean toggle_menu_bar(void)
{
    toggle_widget(GTK_WIDGET(menu_bar), &options->menu_bar);
    return FALSE;
}

gboolean toggle_status_bar(void)
{
    toggle_widget(GTK_WIDGET(status_bar), &options->status_bar);
    return FALSE;
}

/* To take into account the new filename, zoom, angle, symmetry. */
void update_status_bar(void)
{
    gchar *size_str, *state_str;
    const gchar *sym;
    gfloat zoom, angle;

    if (current_image == NULL)
        return;

    if (current_image->ident != NULL) {
        /* Filename and dimensions status, only the first time. */
        set_ident_text(current_image->ident);

        size_str = g_strdup_printf("%dx%d",
                                   current_image->width, current_image->height);
        set_entry_text(entry_size, size_str);
        g_free(size_str);
    }

    /* The state */

    zoom = get_matrix_zoom() * 100.0;
    angle = get_matrix_angle() * 180.0 / PI;
    sym = is_matrix_symmetry()? " /" : "";

    /* We don't want -0.000 */
    if (float_equal(angle, 0.0))
        angle = 0.0;

    state_str = g_strdup_printf(_("%.3f%% (%.3f deg%s)"), zoom, angle, sym);
    set_entry_text(entry_state, state_str);

    g_free(state_str);
}

/* Does the WM support the EWMH protocol? */
static gboolean has_net_wm(void)
{
    return gdk_net_wm_supports(gdk_atom_intern("_NET_WM_STATE_FULLSCREEN",
                                               FALSE));
}

/*
 * On the first map, all widgets are shown, we have to hide
 * those that are not requested.
 */
static gboolean first_map(void)
{
    if (options->menu_bar == FALSE)
        gtk_widget_hide(GTK_WIDGET(menu_bar));

    if (options->status_bar == FALSE)
        gtk_widget_hide(GTK_WIDGET(status_bar));

    if (options->scrollbars == FALSE)
        hide_scrollbars();

    if (options->fullscreen && has_net_wm())
        toggle_fullscreen(TRUE);

    return FALSE;
}

void create_windows(void)
{
    GtkWindow *first_window;
    GtkAccelGroup *accel_group;
    GtkHBox *hbox;

    rt->scr_width = gdk_screen_width();
    rt->scr_height = gdk_screen_height();

    /*
     * The main window: seen when not in fullscreen,
     * or when using the NET WM protocol.
     */
    main_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
    gtk_window_set_title(main_window, "GLiv");
    install_callbacks(main_window);

    /* The window seen in fullscreen mode. */
    fs_window = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
    gtk_window_set_title(fs_window, _("GLiv in fullscreen"));

    /* Make it fullscreen. */
    gtk_window_move(fs_window, 0, 0);
    gtk_window_set_default_size(fs_window, rt->scr_width, rt->scr_height);
    gtk_window_set_decorated(fs_window, FALSE);
    install_callbacks(fs_window);

    /* The widgets. */
    create_gl_widget();
    accel_group = create_menus();
    create_status_bar();

    /* Bind keyboard accelerators to windows. */
    gtk_window_add_accel_group(main_window, accel_group);
    gtk_window_add_accel_group(fs_window, accel_group);

    /* Collection. */
    widgets = GTK_VBOX(gtk_vbox_new(FALSE, 0));

    /* The menu bar on the top. */
    gtk_box_pack_start(GTK_BOX(widgets), GTK_WIDGET(menu_bar), FALSE, FALSE, 0);

    /* The OpenGL widget and the vertical scrollbar under the menu bar. */
    hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0));
    gtk_box_pack_start_defaults(GTK_BOX(hbox), gl_widget);
    gtk_box_pack_start(GTK_BOX(hbox), get_new_scrollbar(FALSE), FALSE, FALSE,
                       0);
    gtk_box_pack_start_defaults(GTK_BOX(widgets), GTK_WIDGET(hbox));

    /* The horizontal scrollbar under them. */
    gtk_box_pack_start(GTK_BOX(widgets), get_new_scrollbar(TRUE), FALSE, FALSE,
                       0);

    /* The status bar in the bottom. */
    gtk_box_pack_end(GTK_BOX(widgets), GTK_WIDGET(status_bar), FALSE, FALSE, 0);

    first_window = (options->fullscreen && !has_net_wm())?
        fs_window : main_window;

    gtk_container_add(GTK_CONTAINER(first_window), GTK_WIDGET(widgets));
    g_signal_connect_after(first_window, "map", G_CALLBACK(first_map), NULL);
    gtk_widget_show_all(GTK_WIDGET(first_window));
}

/*
 * Called when going from fullscreen mode to window mode, or when
 * the displayed image changed in window mode.
 */
void goto_window(GlivImage * im, gboolean force)
{
    gint new_width, new_height;

    if (im == NULL)
        im = current_image;

    if (im == NULL) {
        new_width = 320;
        new_height = 240;
    } else {
        new_width = im->width;
        new_height = im->height;
    }

    if (new_width >= rt->scr_width)
        new_width = 3 * rt->scr_width / 4;

    if (new_height >= rt->scr_height)
        new_height = 3 * rt->scr_height / 4;

    resize_window(new_width, new_height, force);
}

/*
 * Return TRUE if we managed to toggle the fullscreen mode using the
 * NET WM protocol.
 */
static gboolean net_wm_toggle_fullscreen(gboolean enable)
{
    if (get_current_window() != main_window || has_net_wm() == FALSE)
        return FALSE;

    if (enable)
        gtk_window_fullscreen(main_window);
    else
        gtk_window_unfullscreen(main_window);

    return TRUE;
}

void toggle_fullscreen(gboolean enable)
{
    /* We first try using the NET WM protocol, then with the old method. */
    if (net_wm_toggle_fullscreen(enable) == FALSE) {
        GtkWindow *new, *old;

        if (enable) {
            /* Go to fullscreen mode. */
            new = fs_window;
            old = main_window;
        } else {
            /* Go to window mode. */
            new = main_window;
            old = fs_window;
        }

        gtk_widget_show(GTK_WIDGET(new));
        gtk_widget_hide(GTK_WIDGET(old));

        gtk_widget_reparent(GTK_WIDGET(widgets), GTK_WIDGET(new));

    }

    if (enable == FALSE)
        goto_window(NULL, FALSE);

    options->fullscreen = enable;
    schedule_hide_cursor();
}

void update_window_title(void)
{
    const gchar *path;
    gchar *basename;

    path = filename_to_utf8(current_image->node->data);
    basename = g_path_get_basename(path);
    gtk_window_set_title(main_window, basename);
    g_free(basename);
}

GtkWindow *get_current_window(void)
{
    if (GTK_IS_WIDGET(gl_widget) && GTK_WIDGET_VISIBLE(gl_widget))
        return GTK_WINDOW(gtk_widget_get_toplevel(gl_widget));

    return NULL;
}
