/*
 * 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>
 */

/************************
 * User-defined actions *
 ************************/

#include <stdio.h>              /* FILE, fprintf() */
#include <string.h>             /* strchr(), memset() */
#include <stdlib.h>             /* system() */
#include <sys/wait.h>           /* WEXITSTATUS */

#include "gliv.h"
#include "actions.h"
#include "mnemonics.h"
#include "gliv-image.h"
#include "files_list.h"
#include "callbacks.h"
#include "windows.h"
#include "messages.h"
#include "next_image.h"

extern GlivImage *current_image;

typedef struct {
    gchar *name;
    gchar *command;
} action;

static GPtrArray *actions_array = NULL;

static GString *build_command_line(const gchar * command,
                                   const gchar * filename)
{
    GString *cmd_line = g_string_new("");
    const gchar *ptr, *remaining = command;
    gchar *quoted, *tmp;

    for (;;) {
        ptr = strchr(remaining, '%');
        if (ptr == NULL) {
            g_string_append(cmd_line, remaining);
            return cmd_line;
        }

        g_string_append_len(cmd_line, remaining, ptr - remaining);
        ptr++;
        remaining = ptr + 1;

        switch (*ptr) {
        case 'd':
            tmp = g_path_get_dirname(filename);
            quoted = g_shell_quote(tmp);
            g_free(tmp);
            g_string_append(cmd_line, quoted);
            g_free(quoted);
            break;

        case 'b':
            tmp = g_path_get_basename(filename);
            quoted = g_shell_quote(tmp);
            g_free(tmp);
            g_string_append(cmd_line, quoted);
            g_free(quoted);
            break;

        case 'f':
            quoted = g_shell_quote(filename);
            g_string_append(cmd_line, quoted);
            g_free(quoted);
            break;

        case '%':
            g_string_append_c(cmd_line, '%');
            break;

        default:
            g_string_append_len(cmd_line, ptr - 1, 2);
            break;
        }
    }
}

static gint action_on_filename(action * a, const gchar * filename)
{
    gint ret;
    GString *cmd_line = build_command_line(a->command, filename);

    ret = system(cmd_line->str);
    g_string_free(cmd_line, TRUE);

    return ret;
}

static void process_system_retval(action * a, gint retval)
{
    GtkWidget *msg;
    gchar *str;

    if (retval == 0)
        return;

    if (retval < 0) {
        str = _("Failed to create a new process");
        msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
                                     GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
                                     "%s", str);
    } else {
        str = _("The action '%s' terminated with a non-null return code: %d");
        msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
                                     GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
                                     str, a->name, WEXITSTATUS(retval));
    }

    run_modal_dialog(GTK_DIALOG(msg));
    gtk_widget_destroy(msg);
}

static gboolean action_current(action * a)
{
    gint ret;
    gpointer stat_data;

    if (current_image == NULL || current_image->node == NULL)
        return FALSE;

    stat_data = stat_loaded_files(FALSE);

    ret = action_on_filename(a, current_image->node->data);
    process_system_retval(a, ret);

    reload_changed_files(stat_data);

    return FALSE;
}

static gboolean action_every(action * a)
{
    GList *node;
    gint ret = 0;
    gpointer stat_data;
    GlivImage *before = current_image;

    stat_data = stat_loaded_files(FALSE);

    for (node = get_list_head(); node != NULL; node = node->next) {
        gint new_ret = action_on_filename(a, node->data);
        if (new_ret != 0)
            ret = new_ret;
        process_events();
    }

    process_system_retval(a, ret);

    if (before == current_image)
        reload_changed_files(stat_data);
    else {
        /* Don't bother checking files changes if the user changed the image */
        g_free(stat_data);
    }

    return FALSE;
}

static void make_menu(GtkAccelGroup * accel_group, GtkMenuItem * menu_item,
                      GCallback cb, const gchar * menu_name)
{
    const gchar *name;
    GtkMenuItem *item;
    GtkMenu *menu = GTK_MENU(gtk_menu_new());
    gchar *accel_path;
    gint i;

    gtk_menu_item_deselect(menu_item);
    gtk_menu_set_accel_group(menu, accel_group);
    gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), gtk_tearoff_menu_item_new());
    push_mnemonics();

    for (i = 0; i < actions_array->len; i++) {
        action *a = g_ptr_array_index(actions_array, i);

        name = add_mnemonic(a->name);
        item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(name));
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item));

        g_signal_connect_swapped(item, "activate", cb, a);

        accel_path = g_strconcat("<GLiv>/Commands/", menu_name, "/", a->name,
                                 NULL);

        gtk_accel_map_add_entry(accel_path, 0, 0);
        gtk_menu_item_set_accel_path(item, accel_path);
        g_free(accel_path);
    }

    gtk_menu_item_set_submenu(menu_item, GTK_WIDGET(menu));
    gtk_widget_show_all(GTK_WIDGET(menu_item));

    pop_mnemonics();
}

void init_actions(GtkAccelGroup * the_accel_group,
                  GtkMenuItem * current_image_menu_item,
                  GtkMenuItem * every_image_menu_item)
{
    static GtkAccelGroup *accel_group = NULL;
    static GtkMenuItem *current_image_item = NULL;
    static GtkMenuItem *every_image_item = NULL;

    if (the_accel_group != NULL) {
        /* First time */
        accel_group = the_accel_group;
        add_action(_("Open in Gimp"), "gimp-remote -- %f");
    }

    if (current_image_menu_item != NULL)
        current_image_item = current_image_menu_item;

    if (every_image_menu_item != NULL)
        every_image_item = every_image_menu_item;

    if (actions_array == NULL)
        actions_array = g_ptr_array_new();

    make_menu(accel_group, current_image_item, G_CALLBACK(action_current),
              "Action on the current image");

    make_menu(accel_group, every_image_item, G_CALLBACK(action_every),
              "Action on every image");
}


/*** GUI part ***/

#include "glade_actions.h"

enum {
    COLUMN_NAME,
    COLUMN_COMMAND,
    N_COLUMNS
};

static GtkListStore *list_store = NULL;
static GtkTreeIter iter;
static gboolean has_selection = FALSE;

static void get_list_row(GtkTreeIter * where, gchar ** name, gchar ** command)
{
    GValue value;

    memset(&value, 0, sizeof(GValue));
    if (name != NULL) {
        gtk_tree_model_get_value(GTK_TREE_MODEL(list_store), where,
                                 COLUMN_NAME, &value);

        *name = g_strdup(g_value_peek_pointer(&value));
        g_value_unset(&value);
    }

    if (command != NULL) {
        gtk_tree_model_get_value(GTK_TREE_MODEL(list_store), where,
                                 COLUMN_COMMAND, &value);

        *command = g_strdup(g_value_peek_pointer(&value));
        g_value_unset(&value);
    }
}

static gboolean has_action_by_name(const gchar * name)
{
    gchar *action_name = NULL;
    GtkTreeIter iter;

    if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(list_store), &iter)) {
        do {
            get_list_row(&iter, &action_name, NULL);
            if (g_str_equal(name, action_name))
                break;

            g_free(action_name);
            action_name = NULL;
        } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(list_store), &iter));
    }

    g_free(action_name);
    return action_name != NULL;
}

static gboolean edit_action_dialog(gchar ** name, gchar ** command)
{

    GtkWidget *dialog = create_edit_action_dialog();
    GtkEntry *name_entry, *command_entry;
    gboolean duplicate;
    gint response;
    gboolean ok = FALSE;

    name_entry = g_object_get_data(G_OBJECT(dialog), "name_entry");
    command_entry = g_object_get_data(G_OBJECT(dialog), "command_entry");

    if (*name != NULL)
        gtk_entry_set_text(name_entry, *name);

    if (*command != NULL)
        gtk_entry_set_text(command_entry, *command);

    response = run_modal_dialog(GTK_DIALOG(dialog));
    if (response == GTK_RESPONSE_OK) {
        gchar *new_name;

        new_name = gtk_editable_get_chars(GTK_EDITABLE(name_entry), 0, -1);
        if (*name == NULL || g_str_equal(new_name, *name) == FALSE) {
            duplicate = has_action_by_name(new_name);
            if (duplicate) {
                GtkWidget *msg;
                gchar *str = _("The action name '%s' is already used");
                msg = gtk_message_dialog_new(NULL,
                                             GTK_DIALOG_MODAL,
                                             GTK_MESSAGE_WARNING,
                                             GTK_BUTTONS_CLOSE, str, new_name);
                run_modal_dialog(GTK_DIALOG(msg));
                gtk_widget_destroy(msg);
            }
        }

        if (!duplicate) {
            g_free(*name);
            g_free(*command);

            *command = gtk_editable_get_chars(GTK_EDITABLE(command_entry),
                                              0, -1);
            *name = new_name;
            ok = TRUE;
        } else
            g_free(new_name);
    }

    gtk_widget_destroy(dialog);
    return ok;
}

static gboolean on_add_button_clicked(void)
{
    gchar *name = NULL, *command = NULL;
    gboolean ok;

    ok = edit_action_dialog(&name, &command);

    if (ok && name[0] != '\0' && command[0] != '\0') {
        GtkTreeIter new_iter;

        if (has_selection)
            gtk_list_store_insert_after(list_store, &new_iter, &iter);
        else
            gtk_list_store_append(list_store, &new_iter);

        gtk_list_store_set(list_store, &new_iter,
                           COLUMN_NAME, name, COLUMN_COMMAND, command, -1);
    }

    g_free(name);
    g_free(command);
    return FALSE;
}

static gboolean on_property_button_clicked(void)
{

    gchar *name, *command;
    gint ok;

    get_list_row(&iter, &name, &command);
    ok = edit_action_dialog(&name, &command);

    if (ok && name[0] != '\0' && command[0] != '\0')
        gtk_list_store_set(list_store, &iter,
                           COLUMN_NAME, name, COLUMN_COMMAND, command, -1);
    g_free(name);
    g_free(command);
    return FALSE;
}

static gboolean on_delete_button_clicked(void)
{
    gtk_list_store_remove(list_store, &iter);
    return FALSE;
}

static gboolean selection_changed(GtkTreeSelection * selection,
                                  GtkWidget * dialog)
{
    GtkWidget *property_button, *delete_button;

    has_selection = gtk_tree_selection_get_selected(selection, NULL, &iter);

    property_button = g_object_get_data(G_OBJECT(dialog), "property_button");
    delete_button = g_object_get_data(G_OBJECT(dialog), "delete_button");

    if (GTK_WIDGET_IS_SENSITIVE(property_button) != has_selection) {
        gtk_widget_set_sensitive(property_button, has_selection);
        gtk_widget_set_sensitive(delete_button, has_selection);
    }

    return FALSE;
}

#include "glade_actions.c"

static void add_column(GtkTreeView * tree_view, const gchar * name,
                       gint column_id)
{
    GtkTreeViewColumn *column;
    GtkCellRenderer *renderer = gtk_cell_renderer_text_new();

    column = gtk_tree_view_column_new_with_attributes(name, renderer,
                                                      "text", column_id, NULL);

    gtk_tree_view_column_set_resizable(column, TRUE);
    gtk_tree_view_column_set_reorderable(column, TRUE);
    gtk_tree_view_column_set_sort_column_id(column, column_id);

    gtk_tree_view_append_column(tree_view, column);
}

static GtkListStore *make_tree_view(GtkWidget * dialog)
{
    GtkTreeView *tree_view = g_object_get_data(G_OBJECT(dialog), "treeview");
    GtkListStore *list_store;
    GtkTreeIter iter;
    GtkTreeSelection *selection;
    gint i;

    list_store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);

    for (i = 0; i < actions_array->len; i++) {
        action *a = g_ptr_array_index(actions_array, i);

        gtk_list_store_append(list_store, &iter);
        gtk_list_store_set(list_store, &iter,
                           COLUMN_NAME, a->name,
                           COLUMN_COMMAND, a->command, -1);
    }

    gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(list_store));

    add_column(tree_view, _("Name"), COLUMN_NAME);
    add_column(tree_view, _("Command"), COLUMN_COMMAND);

    selection = gtk_tree_view_get_selection(tree_view);
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_SINGLE);
    g_signal_connect(selection, "changed", G_CALLBACK(selection_changed),
                     dialog);

    return list_store;
}

static gboolean read_store(GtkTreeModel * unused_model,
                           GtkTreePath * unused_path, GtkTreeIter * iter)
{
    gchar *name, *command;
    action *a = g_new(action, 1);

    get_list_row(iter, &name, &command);

    a->name = name;
    a->command = command;

    g_ptr_array_add(actions_array, a);

    return FALSE;
}

static void destroy_actions(void)
{
    gint i;

    for (i = 0; i < actions_array->len; i++) {
        action *a = g_ptr_array_index(actions_array, i);

        g_free(a->name);
        g_free(a->command);
        g_free(a);
    }

    g_ptr_array_free(actions_array, TRUE);
    actions_array = NULL;
}

gboolean edit_actions(void)
{
    GtkWidget *dialog = create_actions_dialog();
    gint response;

    list_store = make_tree_view(dialog);
    response = run_modal_dialog(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);

    if (response == GTK_RESPONSE_OK) {
        destroy_actions();
        actions_array = g_ptr_array_new();
        gtk_tree_model_foreach(GTK_TREE_MODEL(list_store),
                               (GtkTreeModelForeachFunc) read_store, NULL);
        init_actions(NULL, NULL, NULL);
    }

    g_object_unref(list_store);
    list_store = NULL;
    has_selection = FALSE;

    return FALSE;
}

/*** glivrc ***/

void add_action(const gchar * name, const gchar * command)
{
    gint i;
    action *a;

    if (actions_array == NULL)
        actions_array = g_ptr_array_new();

    for (i = 0; i < actions_array->len; i++) {
        a = g_ptr_array_index(actions_array, i);

        if (g_str_equal(a->name, name)) {
            g_free(a->command);
            a->command = g_strdup(command);
            return;
        }
    }

    a = g_new(action, 1);
    a->name = g_strdup(name);
    a->command = g_strdup(command);

    g_ptr_array_add(actions_array, a);
}

void write_actions(FILE * file)
{
    gint i;
    action *a;

    if (actions_array == NULL)
        return;

    for (i = 0; i < actions_array->len; i++) {
        a = g_ptr_array_index(actions_array, i);

        fprintf(file, "action_name = %s\n", a->name);
        fprintf(file, "action_command = %s\n\n", a->command);
    }
}
