/*
 * 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@gmail.com>
 */

/************************************
 * The Images and Directories menus *
 ************************************/

#include <string.h>             /* strlen() */

#include "gliv.h"
#include "images_menus.h"
#include "options.h"
#include "menus.h"
#include "messages.h"
#include "next_image.h"
#include "mnemonics.h"
#include "loading.h"
#include "thumbnails.h"
#include "callbacks.h"
#include "tree.h"
#include "timestamp.h"
#include "dirty_gtk.h"
#include "gliv-image.h"
#include "tree_browser.h"

extern options_struct *options;
extern GlivImage *current_image;

/* The progress indicator. */
static GtkMenuItem *rebuilding_entry;

/* The menu entry to cancel the rebuilding. */
static GtkMenuItem *cancel_menu_item;

/*
 * Association between a directory and its GtkMenu.
 * Used by Directories->"Current image directory...".
 */
static GHashTable *dir_menu_hash = NULL;

/* Menus timestamps */
static DECLARE_TIMESTAMP(directories_timestamp);
static DECLARE_TIMESTAMP(images_timestamp);

/*** Functions for both menus. ***/

/* Called at menu creation time. */
void set_rebuilding_entry(GtkMenuItem * item)
{
    rebuilding_entry = item;
}

/* Refresh the percent indicator. */
static void set_menu_indicator(const gchar * name, gint percent)
{
    static gchar *rebuilding = NULL;

    if (name == NULL)
        set_menu_label(rebuilding_entry, "", FALSE);
    else {
        gchar *label;

        if (rebuilding == NULL)
            /* First time. */
            rebuilding = _("Rebuilding:");

        label = g_strdup_printf("%s %s (%d%%)", rebuilding, name, percent);
        set_menu_label(rebuilding_entry, label, FALSE);

        g_free(label);
    }
}

/* Update the percent indicator. */
void set_progress(const gchar * menu, gint * percent, gint number)
{
    static gint total_length;

    /* Yes, that's ugly */
    if (number == -1) {
        /* First time for the menu */
        total_length = tree_count_files();
        return;
    }

    if (menu == NULL) {
        /* Last time for the menu */
        set_menu_indicator(NULL, 0);
        return;
    }

    if ((*percent + 1) * total_length <= 100 * number) {
        *percent = (100 * number) / total_length;
        set_menu_indicator(menu, *percent);
    }
}

/* We don't want to perform the signal lookup for each file. */
static void connect_activate(GtkMenuItem * instance, const gchar * filename)
{
    static guint signal_id = 0;
    GClosure *closure;

    if (signal_id == 0)
        /* First time. */
        signal_id = g_signal_lookup("activate", G_TYPE_FROM_INSTANCE(instance));

    closure = g_cclosure_new_swap(G_CALLBACK(menu_load),
                                  (gpointer) filename, NULL);

    g_signal_connect_closure_by_id(instance, signal_id, 0, closure, FALSE);
}

typedef enum {
    MENU_FILE,                  /* From both menus. */
    MENU_DIR,                   /* From the Images menu. */
    MENU_SUBMENU                /* From the Directories menu. */
} menu_type;

/* Add also a mnemonic and a thumbnail. */
static GtkMenuItem *add_menu_item(DirtyGtkMenu * menu,
                                  tree_item * item, menu_type type)
{
    const gchar *name;
    GtkMenuItem *menu_item;
    GtkImage *thumbnail = NULL;

    if (options->thumbnails && type == MENU_FILE && fill_thumbnail(item))
        thumbnail = GTK_IMAGE(gtk_image_new_from_pixbuf(item->thumb));
    else
        /* Simulate the do_threaded() effect of fill_thumbnail(). */
        process_events();

    if (type == MENU_DIR)
        name = item->path;
    else
        name = item->name;

    if (options->mnemonics && type != MENU_DIR) {
        name = add_mnemonic(name);

        if (thumbnail)
            menu_item =
                GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(name));
        else
            menu_item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(name));

    } else {
        if (thumbnail)
            menu_item = GTK_MENU_ITEM(gtk_image_menu_item_new_with_label(name));
        else
            menu_item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(name));
    }

    if (thumbnail)
        gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
                                      GTK_WIDGET(thumbnail));

    dirty_gtk_menu_append(menu, GTK_WIDGET(menu_item));

    switch (type) {
    case MENU_FILE:
        connect_activate(menu_item, item->path);
        break;

    case MENU_DIR:
        gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE);
        break;

    case MENU_SUBMENU:
        break;
    }

    return menu_item;
}

/*** Directories menu. ***/

static void add_file_from_tree(DirtyGtkMenu * menu, tree_item * item)
{
    static gchar *directories = NULL;
    static gint number = 0, percent = 0;

    if (canceled_using_tree())
        return;

    if (menu == NULL) {
        /* First time for this menu. */

        if (directories == NULL)
            /* First time. */
            directories = _("Directories");

        number = 0;
        percent = 0;
        return;
    }

    add_menu_item(menu, item, MENU_FILE);

    set_progress(directories, &percent, number);
    number++;
}

static DirtyGtkMenu *add_sub_menu(DirtyGtkMenu * parent, tree_item * item)
{
    DirtyGtkMenu *menu;
    GtkMenuItem *menu_item;

    menu_item = add_menu_item(parent, item, MENU_SUBMENU);

    menu = dirty_gtk_menu_new();
    gtk_menu_item_set_submenu(menu_item, GTK_WIDGET(menu->gtk_menu));

    g_hash_table_insert(dir_menu_hash, item->path,
                        dirty_gtk_menu_get_tearoff(menu));

    return menu;
}

static void make_menu_from_tree_rec(GNode * tree, DirtyGtkMenu * parent)
{
    tree_item *item;
    DirtyGtkMenu *menu;

    if (canceled_using_tree())
        return;

    if (G_NODE_IS_LEAF(tree)) {
        add_file_from_tree(parent, tree->data);
        return;
    }

    item = tree->data;
    menu = add_sub_menu(parent, item);

    push_mnemonics();

    g_node_children_foreach(tree, G_TRAVERSE_ALL,
                            (GNodeForeachFunc) make_menu_from_tree_rec, menu);

    dirty_gtk_menu_release(menu);
    pop_mnemonics();
}

static gboolean open_current_dir_menu(void)
{
    if (dir_menu_hash && current_image && current_image->node) {
        gchar *path = g_strdup(current_image->node->data);
        GtkTearoffMenuItem *tearoff;

        do {
            gchar *new_path = g_path_get_dirname(path);

            g_free(path);
            path = new_path;

            tearoff = g_hash_table_lookup(dir_menu_hash, path);
        } while (!tearoff && !g_str_equal(path, "/") &&
                 !g_str_equal(path, "."));

        g_free(path);

        if (tearoff) {
            GtkWidget *parent_menu = gtk_widget_get_parent(GTK_WIDGET(tearoff));
            gtk_widget_realize(parent_menu);
            gtk_menu_item_activate(GTK_MENU_ITEM(tearoff));
        }
    }

    return FALSE;
}

static void add_current_dir_entry(DirtyGtkMenu * menu)
{
    GtkMenuItem *item;
    const gchar *label;

    label = add_mnemonic(_("Current image directory..."));

    item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(label));
    g_signal_connect_swapped(item, "activate",
                             G_CALLBACK(open_current_dir_menu), NULL);

    gtk_widget_show(GTK_WIDGET(item));
    dirty_gtk_menu_append(menu, GTK_WIDGET(item));

    if (dir_menu_hash)
        g_hash_table_destroy(dir_menu_hash);

    dir_menu_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);

    g_hash_table_insert(dir_menu_hash, ".", dirty_gtk_menu_get_tearoff(menu));
    g_hash_table_insert(dir_menu_hash, "/", dirty_gtk_menu_get_tearoff(menu));
}

/*** Images menu. ***/

static gboolean add_file_item(GNode * tree, DirtyGtkMenu * parent_menu)
{
    static gchar *images = NULL;
    static gint number = 0, percent = 0;
    GNode *sibling;

    if (canceled_using_tree())
        return TRUE;

    if (parent_menu == NULL) {
        /* First time for this menu. */

        if (images == NULL)
            /* First time. */
            images = _("Images");

        number = 0;
        percent = 0;
        return FALSE;
    }

    /* Check if it is the first image in its directory. */
    sibling = g_node_prev_sibling(tree);
    if (sibling == NULL && tree->parent != NULL)
        add_menu_item(parent_menu, tree->parent->data, MENU_DIR);

    add_menu_item(parent_menu, tree->data, MENU_FILE);

    set_progress(images, &percent, number);
    number++;

    return FALSE;
}

/*** Menus builders. ***/

static DirtyGtkMenu *begin_rebuild(GtkMenuItem * root, GCallback func)
{
    static DirtyGtkMenu *menu;
    GtkWidget *rebuilder, *browser;

    if (root != NULL)
        gtk_menu_item_deselect(root);

    menu = dirty_gtk_menu_new();

    rebuilder =
        gtk_menu_item_new_with_mnemonic(add_mnemonic(_("Rebuild this menu")));

    g_signal_connect_swapped(rebuilder, "activate", func, NULL);
    dirty_gtk_menu_append(menu, rebuilder);

    browser =
        gtk_menu_item_new_with_mnemonic(add_mnemonic
                                        (_("Open thumbnails browser...")));

    g_signal_connect(browser, "activate", show_tree_browser, NULL);
    dirty_gtk_menu_append(menu, browser);

    gtk_menu_item_set_submenu(root, GTK_WIDGET(menu->gtk_menu));

    return menu;
}

GNode *get_tree(void)
{
    if (currently_loading())
        /*
         * There is no problem in rebuilding the images menus during
         * a loading, it would just make the loading too long.
         */
        return NULL;

    return make_tree();
}

#define REBUILD_START(name, func, timestamp)                                 \
    do {                                                                     \
        if (root != NULL) {                                                  \
            menu_item = root;                                                \
            return TRUE;                                                     \
        }                                                                    \
                                                                             \
        if (more_recent_than_tree(timestamp))                                \
            /* The menu is already up to date. */                            \
            return TRUE;                                                     \
                                                                             \
        tree = get_tree();                                                   \
        if (tree == NULL) {                                                  \
            touch(&timestamp);                                               \
            return TRUE;                                                     \
        }                                                                    \
                                                                             \
        gtk_widget_set_sensitive(GTK_WIDGET(cancel_menu_item), TRUE);        \
        reset_mnemonics();                                                   \
        menu = begin_rebuild(menu_item, G_CALLBACK(func));                   \
        set_menu_indicator(name, 0);                                         \
        set_progress(NULL, NULL, -1);                                        \
    } while (0)

static gboolean rebuild_end(GtkMenuItem * root, timestamp_t * ts,
                            GtkMenuItem * menu_item, DirtyGtkMenu * menu)
{
    if (root != NULL)
        gtk_menu_item_deselect(root);

    dirty_gtk_menu_append(menu, gtk_tearoff_menu_item_new());

    set_progress(NULL, NULL, 0);
    gtk_widget_set_sensitive(GTK_WIDGET(cancel_menu_item), FALSE);

    if (canceled_using_tree())
        reset_timestamp(ts);
    else
        touch(ts);

    end_using_tree();
    reset_mnemonics();
    gtk_widget_show_all(GTK_WIDGET(menu_item));
    dirty_gtk_menu_release(menu);
    return *ts != 0;
}

gboolean rebuild_directories(GtkMenuItem * root)
{
    static GtkMenuItem *menu_item;
    DirtyGtkMenu *menu;
    GNode *tree, *child;
    gchar *prefix, *old_name;
    tree_item *item;

    REBUILD_START(_("Directories"), rebuild_directories, directories_timestamp);
    add_current_dir_entry(menu);

    /* Build the menu. */
    add_file_from_tree(NULL, NULL);

    item = tree->data;
    prefix = item->path;

    if (prefix[0] == '\0' || prefix[1] == '\0') {
        for (child = g_node_first_child(tree); child; child = child->next) {
            item = child->data;
            old_name = item->name;
            item->name = g_build_filename(prefix, item->name, NULL);

            make_menu_from_tree_rec(child, menu);

            g_free(item->name);
            item->name = old_name;
        }
    } else
        make_menu_from_tree_rec(tree, menu);

    return rebuild_end(root, &directories_timestamp, menu_item, menu);
}

gboolean rebuild_images(GtkMenuItem * root)
{
    static GtkMenuItem *menu_item;
    DirtyGtkMenu *menu;
    GNode *tree;

    REBUILD_START(_("Images"), rebuild_images, images_timestamp);

    /* Build the menu. */
    add_file_item(NULL, NULL);
    g_node_traverse(tree, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
                    (GNodeTraverseFunc) add_file_item, menu);

    return rebuild_end(root, &images_timestamp, menu_item, menu);
}

/* Rebuild both menus. */
gboolean rebuild_images_menus(void)
{
    return rebuild_directories(NULL) && rebuild_images(NULL);
}

void set_stop_rebuilding_menu(GtkMenuItem * item)
{
    cancel_menu_item = item;
    gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
}

void obsolete_menus(void)
{
    reset_timestamp(&directories_timestamp);
    reset_timestamp(&images_timestamp);
}

void cond_rebuild_menus(void)
{
    if (directories_timestamp)
        rebuild_directories(NULL);

    if (images_timestamp)
        rebuild_images(NULL);

    cond_rebuild_tree_browser();
}
