/* Python plugin for Claws-Mail
 * Copyright (C) 2009 Holger Berndt
 *
 * 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/>.
 */

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

#include <Python.h>

#include "common/hooks.h"
#include "common/plugin.h"
#include "common/version.h"
#include "common/utils.h"
#include "gtk/menu.h"
#include "main.h"
#include "mainwindow.h"
#include "prefs_toolbar.h"

#include "python-shell.h"
#include "python-hooks.h"
#include "clawsmailmodule.h"

#include "gettext.h"

#define PYTHON_SCRIPTS_BASE_DIR "python-scripts"
#define PYTHON_SCRIPTS_MAIN_DIR "main"
#define PYTHON_SCRIPTS_COMPOSE_DIR "compose"
#define PYTHON_SCRIPTS_AUTO_DIR "auto"
#define PYTHON_SCRIPTS_AUTO_STARTUP "startup"
#define PYTHON_SCRIPTS_AUTO_SHUTDOWN "shutdown"
#define PYTHON_SCRIPTS_AUTO_COMPOSE "compose_any"
#define PYTHON_SCRIPTS_ACTION_PREFIX "Tools/PythonScripts/"

static GSList *menu_id_list = NULL;
static GSList *python_mainwin_scripts_id_list = NULL;
static GSList *python_mainwin_scripts_names = NULL;
static GSList *python_compose_scripts_names = NULL;

static GtkWidget *python_console = NULL;

static guint hook_compose_create;

static gboolean python_console_delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
  MainWindow *mainwin;
  GtkToggleAction *action;

  mainwin =  mainwindow_get_mainwindow();
  action = GTK_TOGGLE_ACTION(gtk_action_group_get_action(mainwin->action_group, "Tools/ShowPythonConsole"));
  gtk_toggle_action_set_active(action, FALSE);
  return TRUE;
}

static void setup_python_console(void)
{
  GtkWidget *vbox;
  GtkWidget *console;

  python_console = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_set_size_request(python_console, 600, 400);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(python_console), vbox);

  console = parasite_python_shell_new();
  gtk_box_pack_start(GTK_BOX(vbox), console, TRUE, TRUE, 0);

  g_signal_connect(python_console, "delete-event", G_CALLBACK(python_console_delete_event), NULL);

  gtk_widget_show_all(python_console);

  parasite_python_shell_focus(PARASITE_PYTHON_SHELL(console));
}

static void show_hide_python_console(GtkToggleAction *action, gpointer callback_data)
{
  if(gtk_toggle_action_get_active(action)) {
    if(!python_console)
      setup_python_console();
    gtk_widget_show(python_console);
  }
  else {
    gtk_widget_hide(python_console);
  }
}

static void remove_python_scripts_menus(void)
{
  GSList *walk;
  MainWindow *mainwin;

  mainwin =  mainwindow_get_mainwindow();

  /* toolbar */
  for(walk = python_mainwin_scripts_names; walk; walk = walk->next)
    prefs_toolbar_unregister_plugin_item(TOOLBAR_MAIN, "Python", walk->data);

  /* ui */
  for(walk = python_mainwin_scripts_id_list; walk; walk = walk->next)
      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
  g_slist_free(python_mainwin_scripts_id_list);
  python_mainwin_scripts_id_list = NULL;

  /* actions */
  for(walk = python_mainwin_scripts_names; walk; walk = walk->next) {
    GtkAction *action;
    gchar *entry;
    entry = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
    action = gtk_action_group_get_action(mainwin->action_group, entry);
    g_free(entry);
    if(action)
      gtk_action_group_remove_action(mainwin->action_group, action);
    g_free(walk->data);
  }
  g_slist_free(python_mainwin_scripts_names);
  python_mainwin_scripts_names = NULL;

  /* compose scripts */
  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
    prefs_toolbar_unregister_plugin_item(TOOLBAR_COMPOSE, "Python", walk->data);
    g_free(walk->data);
  }
  g_slist_free(python_compose_scripts_names);
  python_compose_scripts_names = NULL;
}

static gchar* extract_filename(const gchar *str)
{
  gchar *filename;

  filename = g_strrstr(str, "/");
  if(!filename || *(filename+1) == '\0') {
    debug_print("Error: Could not extract filename from %s\n", str);
    return NULL;
  }
  filename++;
  return filename;
}

static void run_script_file(const gchar *filename, Compose *compose)
{
  FILE *fp;
  fp = fopen(filename, "r");
  if(!fp) {
    g_print("Error: Could not open file '%s'\n", filename);
    return;
  }
  put_composewindow_into_module(compose);
  PyRun_SimpleFile(fp, filename);
  fclose(fp);
}

static void run_auto_script_file_if_it_exists(const gchar *autofilename, Compose *compose)
{
  gchar *auto_filepath;

  /* execute auto/autofilename, if it exists */
  auto_filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_AUTO_DIR, G_DIR_SEPARATOR_S, autofilename, NULL);
  if(file_exist(auto_filepath, FALSE))
    run_script_file(auto_filepath, compose);
  g_free(auto_filepath);
}

static void python_mainwin_script_callback(GtkAction *action, gpointer data)
{
  char *filename;

  filename = extract_filename(data);
  if(!filename)
    return;
  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_MAIN_DIR, G_DIR_SEPARATOR_S, filename, NULL);
  run_script_file(filename, NULL);
  g_free(filename);
}

typedef struct _ComposeActionData ComposeActionData;
struct _ComposeActionData {
  gchar *name;
  Compose *compose;
};

static void python_compose_script_callback(GtkAction *action, gpointer data)
{
  char *filename;
  ComposeActionData *dat = (ComposeActionData*)data;

  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S, dat->name, NULL);
  run_script_file(filename, dat->compose);

  g_free(filename);
}

static void mainwin_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
{
	gchar *script;
	script = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, item_name, NULL);
	python_mainwin_script_callback(NULL, script);
	g_free(script);
}

static void compose_toolbar_callback(gpointer parent, const gchar *item_name, gpointer data)
{
  gchar *filename;

  filename = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_COMPOSE_DIR, G_DIR_SEPARATOR_S,
      item_name, NULL);
  run_script_file(filename, (Compose*)parent);
  g_free(filename);
}

static void make_sure_script_directory_exists(const gchar *subdir)
{
  char *dir;
  dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, subdir, NULL);
  if(!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
    if(g_mkdir(dir, 0777) != 0)
      debug_print("Python plugin: Could not create directory '%s'\n", dir);
  }
  g_free(dir);
}

static void make_sure_directories_exist(void)
{
  make_sure_script_directory_exists("");
  make_sure_script_directory_exists(PYTHON_SCRIPTS_MAIN_DIR);
  make_sure_script_directory_exists(PYTHON_SCRIPTS_COMPOSE_DIR);
  make_sure_script_directory_exists(PYTHON_SCRIPTS_AUTO_DIR);
}

static void migrate_scripts_out_of_base_dir(void)
{
  char *base_dir;
  GDir *dir;
  const char *filename;
  gchar *dest_dir;

  base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, NULL);
  dir = g_dir_open(base_dir, 0, NULL);
  g_free(base_dir);
  if(!dir)
    return;

  dest_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S,
      PYTHON_SCRIPTS_MAIN_DIR, NULL);
  if(!g_file_test(dest_dir, G_FILE_TEST_IS_DIR)) {
    if(g_mkdir(dest_dir, 0777) != 0) {
      g_free(dest_dir);
      g_dir_close(dir);
      return;
    }
  }

  while((filename = g_dir_read_name(dir)) != NULL) {
    gchar *filepath;
    filepath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, filename, NULL);
    if(g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
      gchar *dest_file;
      dest_file = g_strconcat(dest_dir, G_DIR_SEPARATOR_S, filename, NULL);
      if(move_file(filepath, dest_file, FALSE) == 0)
        g_print("Python plugin: Moved file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
      else
        g_print("Python plugin: Warning: Could not move file '%s' to %s subdir\n", filename, PYTHON_SCRIPTS_MAIN_DIR);
      g_free(dest_file);
    }
    g_free(filepath);
  }
  g_dir_close(dir);
  g_free(dest_dir);
}


static void create_mainwindow_menus_and_items(GSList *filenames, gint num_entries)
{
  MainWindow *mainwin;
  gint ii;
  GSList *walk;
  GtkActionEntry *entries;

  /* create menu items */
  entries = g_new0(GtkActionEntry, num_entries);
  ii = 0;
  mainwin =  mainwindow_get_mainwindow();
  for(walk = filenames; walk; walk = walk->next) {
    entries[ii].name = g_strconcat(PYTHON_SCRIPTS_ACTION_PREFIX, walk->data, NULL);
    entries[ii].label = walk->data;
    entries[ii].callback = G_CALLBACK(python_mainwin_script_callback);
    gtk_action_group_add_actions(mainwin->action_group, &(entries[ii]), 1, (gpointer)entries[ii].name);
    ii++;
  }
  for(ii = 0; ii < num_entries; ii++) {
    guint id;

    python_mainwin_scripts_names = g_slist_prepend(python_mainwin_scripts_names, g_strdup(entries[ii].label));
    MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
        entries[ii].name, GTK_UI_MANAGER_MENUITEM, id)
    python_mainwin_scripts_id_list = g_slist_prepend(python_mainwin_scripts_id_list, GUINT_TO_POINTER(id));

    prefs_toolbar_register_plugin_item(TOOLBAR_MAIN, "Python", entries[ii].label, mainwin_toolbar_callback, NULL);
  }

  g_free(entries);
}


/* this function doesn't really create menu items, but prepares a list that can be used
 * in the compose create hook. It does however register the scripts for the toolbar editor */
static void create_compose_menus_and_items(GSList *filenames)
{
  GSList *walk;
  for(walk = filenames; walk; walk = walk->next) {
    python_compose_scripts_names = g_slist_prepend(python_compose_scripts_names, g_strdup((gchar*)walk->data));
    prefs_toolbar_register_plugin_item(TOOLBAR_COMPOSE, "Python", (gchar*)walk->data, compose_toolbar_callback, NULL);
  }
}

static GtkActionEntry compose_tools_python_actions[] = {
    {"Tools/PythonScripts", NULL, N_("Python scripts") },
};

static void ComposeActionData_destroy_cb(gpointer data)
{
  ComposeActionData *dat = (ComposeActionData*)data;
  g_free(dat->name);
  g_free(dat);
}

static gboolean my_compose_create_hook(gpointer cw, gpointer data)
{
  gint ii;
  GSList *walk;
  GtkActionEntry *entries;
  GtkActionGroup *action_group;
  Compose *compose = (Compose*)cw;
  guint num_entries = g_slist_length(python_compose_scripts_names);

  action_group = gtk_action_group_new("PythonPlugin");
  gtk_action_group_add_actions(action_group, compose_tools_python_actions, 1, NULL);
  entries = g_new0(GtkActionEntry, num_entries);
  ii = 0;
  for(walk = python_compose_scripts_names; walk; walk = walk->next) {
    ComposeActionData *dat;

    entries[ii].name = walk->data;
    entries[ii].label = walk->data;
    entries[ii].callback = G_CALLBACK(python_compose_script_callback);

    dat = g_new0(ComposeActionData, 1);
    dat->name = g_strdup(walk->data);
    dat->compose = compose;

    gtk_action_group_add_actions_full(action_group, &(entries[ii]), 1, dat, ComposeActionData_destroy_cb);
    ii++;
  }
  gtk_ui_manager_insert_action_group(compose->ui_manager, action_group, 0);

  MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/Tools", "PythonScripts",
      "Tools/PythonScripts", GTK_UI_MANAGER_MENU)

  for(ii = 0; ii < num_entries; ii++) {
    MENUITEM_ADDUI_MANAGER(compose->ui_manager, "/Menu/" PYTHON_SCRIPTS_ACTION_PREFIX, entries[ii].label,
        entries[ii].name, GTK_UI_MANAGER_MENUITEM)
  }

  g_free(entries);

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_COMPOSE, compose);

  return FALSE;
}


static void refresh_scripts_in_dir(const gchar *subdir, ToolbarType toolbar_type)
{
  char *scripts_dir;
  GDir *dir;
  GError *error = NULL;
  const char *filename;
  GSList *filenames = NULL;
  GSList *walk;
  gint num_entries;

  scripts_dir = g_strconcat(get_rc_dir(),
      G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR,
      G_DIR_SEPARATOR_S, subdir,
      NULL);
  debug_print("Refreshing: %s\n", scripts_dir);

  dir = g_dir_open(scripts_dir, 0, &error);
  g_free(scripts_dir);

  if(!dir) {
    g_print("Could not open directory '%s': %s\n", subdir, error->message);
    g_error_free(error);
    return;
  }

  /* get filenames */
  num_entries = 0;
  while((filename = g_dir_read_name(dir)) != NULL) {
    char *fn;

    fn = g_strdup(filename);
    filenames = g_slist_prepend(filenames, fn);
    num_entries++;
  }
  g_dir_close(dir);

  if(toolbar_type == TOOLBAR_MAIN)
    create_mainwindow_menus_and_items(filenames, num_entries);
  else if(toolbar_type == TOOLBAR_COMPOSE)
    create_compose_menus_and_items(filenames);

  /* cleanup */
  for(walk = filenames; walk; walk = walk->next)
    g_free(walk->data);
  g_slist_free(filenames);
}

static void browse_python_scripts_dir(GtkAction *action, gpointer data)
{
  gchar *argv[2];
  gboolean retval;
  GError *error = NULL;
  argv[0] = "xdg-open";
  argv[1] = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, PYTHON_SCRIPTS_BASE_DIR, G_DIR_SEPARATOR_S, NULL);
  retval = gdk_spawn_on_screen(gdk_screen_get_default(), NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
  g_free(argv[1]);
  if(!retval) {
    debug_print("Could not open scripts dir browser: '%s'\n", error->message);
    g_error_free(error);
  }
}

static void refresh_python_scripts_menus(GtkAction *action, gpointer data)
{
  remove_python_scripts_menus();

  migrate_scripts_out_of_base_dir();

  refresh_scripts_in_dir(PYTHON_SCRIPTS_MAIN_DIR, TOOLBAR_MAIN);
  refresh_scripts_in_dir(PYTHON_SCRIPTS_COMPOSE_DIR, TOOLBAR_COMPOSE);
}

static GtkToggleActionEntry mainwindow_tools_python_toggle[] = {
    {"Tools/ShowPythonConsole", NULL, N_("Show Python console..."),
        NULL, NULL, G_CALLBACK(show_hide_python_console), FALSE},
};

static GtkActionEntry mainwindow_tools_python_actions[] = {
    {"Tools/PythonScripts", NULL, N_("Python scripts") },
    {"Tools/PythonScripts/Refresh", NULL, N_("Refresh"),
        NULL, NULL, G_CALLBACK(refresh_python_scripts_menus) },
    {"Tools/PythonScripts/Browse", NULL, "Browse",
        NULL, NULL, G_CALLBACK(browse_python_scripts_dir) },
    {"Tools/PythonScripts/---", NULL, "---" },
};

void python_menu_init(void)
{
  MainWindow *mainwin;
  guint id;

  mainwin =  mainwindow_get_mainwindow();

  gtk_action_group_add_toggle_actions(mainwin->action_group, mainwindow_tools_python_toggle, 1, mainwin);
  gtk_action_group_add_actions(mainwin->action_group, mainwindow_tools_python_actions, 3, mainwin);

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "ShowPythonConsole",
      "Tools/ShowPythonConsole", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools", "PythonScripts",
      "Tools/PythonScripts", GTK_UI_MANAGER_MENU, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Refresh",
      "Tools/PythonScripts/Refresh", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Browse",
      "Tools/PythonScripts/Browse", GTK_UI_MANAGER_MENUITEM, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  MENUITEM_ADDUI_ID_MANAGER(mainwin->ui_manager, "/Menu/Tools/PythonScripts", "Separator1",
      "Tools/PythonScripts/---", GTK_UI_MANAGER_SEPARATOR, id)
  menu_id_list = g_slist_prepend(menu_id_list, GUINT_TO_POINTER(id));

  refresh_python_scripts_menus(NULL, NULL);
}

void python_menu_done(void)
{
  MainWindow *mainwin;

  mainwin = mainwindow_get_mainwindow();

  if(mainwin && !claws_is_exiting()) {
    GSList *walk;

    remove_python_scripts_menus();

    for(walk = menu_id_list; walk; walk = walk->next)
      gtk_ui_manager_remove_ui(mainwin->ui_manager, GPOINTER_TO_UINT(walk->data));
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/ShowPythonConsole", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Refresh", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/Browse", 0);
    MENUITEM_REMUI_MANAGER(mainwin->ui_manager, mainwin->action_group, "Tools/PythonScripts/---", 0);
  }
}

gint plugin_init(gchar **error)
{
#ifdef G_OS_UNIX
	bindtextdomain(TEXTDOMAIN, LOCALEDIR);
#else
	bindtextdomain(TEXTDOMAIN, get_locale_dir());
#endif
  bind_textdomain_codeset(TEXTDOMAIN, "UTF-8");

  /* Version check */
  if(!check_plugin_version(MAKE_NUMERIC_VERSION(3,7,3,14),
			   VERSION_NUMERIC, _("Python"), error))
    return -1;

  /* load hooks */
  hook_compose_create = hooks_register_hook(COMPOSE_CREATED_HOOKLIST, my_compose_create_hook, NULL);
  if(hook_compose_create == (guint)-1) {
    *error = g_strdup(_("Failed to register compose create hook int the Python plugin"));
    return -1;
  }

  /* script directories */
  make_sure_directories_exist();

  /* initialize python interpreter */
  Py_Initialize();

  /* initialize python interactive shell */
  parasite_python_init();

  /* initialize Claws Mail Python module */
  claws_mail_python_init();

  /* load menu options */
  python_menu_init();

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_STARTUP, NULL);

  debug_print("Python plugin loaded\n");

  return 0;
}

gboolean plugin_done(void)
{
  hooks_unregister_hook(COMPOSE_CREATED_HOOKLIST, hook_compose_create);

  run_auto_script_file_if_it_exists(PYTHON_SCRIPTS_AUTO_SHUTDOWN, NULL);

  python_menu_done();

  if(python_console) {
    gtk_widget_destroy(python_console);
    python_console = NULL;
  }

  /* finialize python interpreter */
  Py_Finalize();

  debug_print("Python plugin done and unloaded.\n");
  return FALSE;
}

const gchar *plugin_name(void)
{
  return _("Python");
}

const gchar *plugin_desc(void)
{
  return _("This plugin provides Python integration features.\n"
           "\nFeedback to <berndth@gmx.de> is welcome.");
}

const gchar *plugin_type(void)
{
  return "GTK2";
}

const gchar *plugin_licence(void)
{
  return "GPL3+";
}

const gchar *plugin_version(void)
{
  return PLUGINVERSION;
}

struct PluginFeature *plugin_provides(void)
{
  static struct PluginFeature features[] =
    { {PLUGIN_UTILITY, N_("Python integration")},
      {PLUGIN_NOTHING, NULL}};
  return features;
}
