/*
 *  nautilus-wipe - a nautilus extension to wipe file(s)
 * 
 *  Copyright (C) 2009-2022 Colomban Wendling <ban@herbesfolles.org>
 *
 *  This library 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 library 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
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

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

#include "nw-extension.h"

#include "nw-api-impl.h"

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>

#ifdef NW_NAUTILUS_IS_NAUTILUS_4
# define GdkWindow void
# define GtkWindow void
# define GtkWidget void
# define GTK_STOCK_DELETE "edit-delete"
# define GTK_STOCK_CLEAR  "edit-clear"
#else
# include <gtk/gtk.h>
# ifdef GDK_WINDOWING_X11
#  include <gdk/gdkx.h>
# endif
# ifdef GDK_WINDOWING_WAYLAND
#  include <gdk/gdkwayland.h>
# endif
#endif

#include "nw-path-list.h"
#include "nw-fill-operation-utils.h"
#include "nw-compat-nautilus.h"


/* private prototypes */
static void   nw_extension_menu_provider_iface_init   (NautilusMenuProviderIface *iface);


#define ITEM_DATA_PATHS_KEY       "Nw::Extension::paths"
#define ITEM_DATA_WINDOW_KEY      "Nw::Extension::parent-window"



GQuark
nw_extension_error_quark (void)
{
  static volatile gsize quark = 0;
  
  if (g_once_init_enter (&quark)) {
    GQuark q = g_quark_from_static_string ("NwExtensionError");
    
    g_once_init_leave (&quark, q);
  }
  
  return (GQuark) quark;
}

/* we want to export the nw_extension_register_type() symbol, so trick to have
 * the auto-generated one use another name */
#define nw_extension_register_type nw_extension_register_type_internal
G_DEFINE_DYNAMIC_TYPE_EXTENDED (NwExtension,
                                nw_extension,
                                G_TYPE_OBJECT,
                                0,
                                G_IMPLEMENT_INTERFACE_DYNAMIC (NAUTILUS_TYPE_MENU_PROVIDER,
                                                               nw_extension_menu_provider_iface_init))
#undef nw_extension_register_type

GType
nw_extension_register_type (GTypeModule *module)
{
  nw_extension_register_type_internal (module);
  return nw_extension_get_type ();
}



/* this has to match GdkWaylandWindowExported */
typedef void (*HandleReady) (GdkWindow   *window,
                             const gchar *handle,
                             gpointer     user_data);

static void
get_native_handle (GtkWindow     *window,
                   HandleReady    callback,
                   gpointer       user_data,
                   GDestroyNotify destroy_func)
{
#ifndef NW_NAUTILUS_IS_NAUTILUS_4
  gchar      *native_window = NULL;
  GdkWindow  *backend_window = NULL;
  
  if (window) {
    backend_window = gtk_widget_get_window (GTK_WIDGET (window));
    
#ifdef GDK_WINDOWING_X11
    if (GDK_IS_X11_WINDOW (backend_window)) {
      native_window = g_strdup_printf ("%lu", gdk_x11_window_get_xid (backend_window));
    }
#endif
#ifdef GDK_WINDOWING_WAYLAND
    if (GDK_IS_WAYLAND_WINDOW (backend_window)) {
      if (gdk_wayland_window_export_handle (backend_window, callback,
                                            user_data, destroy_func)) {
        return;
      }
    }
#endif
  }
  callback (backend_window, native_window, user_data);
  g_free (native_window);
#else /* NW_NAUTILUS_IS_NAUTILUS_4 */
  (void) window;
  callback (NULL, NULL, user_data);
#endif /* NW_NAUTILUS_IS_NAUTILUS_4 */
  destroy_func (user_data);
}

typedef struct
{
  gchar *op_type;
  GList *paths;
} NativeHandlerReadyData;

static void
native_handle_ready_data_free (gpointer ptr)
{
  NativeHandlerReadyData *data = ptr;
  
  g_free (data->op_type);
  g_list_free_full (data->paths, g_free);
  g_free (data);
}

#ifdef GDK_WINDOWING_WAYLAND
static void
watch_child (GPid     pid,
             gint     status G_GNUC_UNUSED,
             gpointer user_data)
{
  GdkWindow *window = user_data;
  
  if (GDK_IS_WAYLAND_WINDOW (window)) {
    gdk_wayland_window_unexport_handle (window);
  }

  g_spawn_close_pid (pid);
}
#endif

static void
native_handle_ready (GdkWindow   *window,
                     const gchar *parent_handle,
                     gpointer     user_data)
{
  NativeHandlerReadyData *data        = user_data;
  GError                 *err         = NULL;
  GPtrArray              *argv        = g_ptr_array_new_with_free_func (g_free);
  GSpawnFlags             spawn_flags = G_SPAWN_DEFAULT;
  GPid                   *pid_ptr     = NULL;
#ifdef GDK_WINDOWING_WAYLAND
  GPid                    pid;
#endif
  
#ifndef GDK_WINDOWING_WAYLAND
  (void) window;
#endif
  
  g_ptr_array_add (argv, g_build_filename (LIBEXECDIR, "nautilus-wipe", NULL));
  g_ptr_array_add (argv, g_strconcat ("--", data->op_type, NULL));
  if (parent_handle) {
    g_ptr_array_add (argv, g_strconcat ("--native-parent=", parent_handle, NULL));
  }
  
  for (GList *paths = data->paths; paths; paths = paths->next) {
    g_ptr_array_add (argv, g_strdup (paths->data));
  }
  
  g_ptr_array_add (argv, NULL);
  
#ifdef GDK_WINDOWING_WAYLAND
  if (parent_handle && GDK_IS_WAYLAND_WINDOW (window)) {
    spawn_flags |= G_SPAWN_DO_NOT_REAP_CHILD;
    pid_ptr = &pid;
  }
#endif
  /* TODO: use g_app_info_launch_uris() or similar? */
  if (! g_spawn_async (NULL, (gchar **) argv->pdata, NULL, spawn_flags,
                       NULL, NULL, pid_ptr, &err)) {
    /* TODO: show the error somehow? */
    g_warning ("failed to spawn helper: %s", err->message);
    g_error_free (err);
#ifdef GDK_WINDOWING_WAYLAND
    if (parent_handle && GDK_IS_WAYLAND_WINDOW (window)) {
      gdk_wayland_window_unexport_handle (window);
    }
#endif
  }
#ifdef GDK_WINDOWING_WAYLAND
  else if (parent_handle && pid_ptr && GDK_IS_WAYLAND_WINDOW (window)) {
    g_child_watch_add (*pid_ptr, watch_child, window);
  }
#endif
  
  g_ptr_array_free (argv, TRUE);
}

/* Runs an operation calling the helper with appropriate arguments */
static void
nw_extension_run_operation (GtkWindow    *parent,
                            const gchar  *type,
                            GList        *paths)
{
  NativeHandlerReadyData *data;
  
  data = g_malloc0 (sizeof *data);
  data->op_type = g_strdup (type);
  data->paths = nw_path_list_copy (paths);
  
  get_native_handle (parent, native_handle_ready,
                     data, native_handle_ready_data_free);
}

static void
wipe_menu_item_activate_handler (GObject *item,
                                 gpointer data)
{
  nw_extension_run_operation (g_object_get_data (item, ITEM_DATA_WINDOW_KEY),
                              "wipe",
                              g_object_get_data (item, ITEM_DATA_PATHS_KEY));
}

static NautilusMenuItem *
create_wipe_menu_item (NautilusMenuProvider *provider,
                       const gchar          *item_name,
                       GtkWidget            *window,
                       GList                *paths)
{
  NautilusMenuItem *item;
  
  item = nautilus_menu_item_new (item_name,
                                 _("Wipe"),
                                 _("Delete each selected item and overwrite its data"),
                                 GTK_STOCK_DELETE);
  g_object_set_data (G_OBJECT (item), ITEM_DATA_WINDOW_KEY, window);
  g_object_set_data_full (G_OBJECT (item), ITEM_DATA_PATHS_KEY,
                          nw_path_list_copy (paths),
                          (GDestroyNotify) nw_path_list_free);
  g_signal_connect (item, "activate",
                    G_CALLBACK (wipe_menu_item_activate_handler), NULL);
  
  return item;
}

static void
fill_menu_item_activate_handler (GObject *item,
                                 gpointer data)
{
  nw_extension_run_operation (g_object_get_data (item, ITEM_DATA_WINDOW_KEY),
                              "fill",
                              g_object_get_data (item, ITEM_DATA_PATHS_KEY));
}

static NautilusMenuItem *
create_fill_menu_item (NautilusMenuProvider *provider,
                       const gchar          *item_name,
                       GtkWidget            *window,
                       GList                *files)
{
  NautilusMenuItem *item        = NULL;
  GList            *mountpoints = NULL;
  GList            *folders     = NULL;
  GError           *err         = NULL;
  
  if (! nw_fill_operation_filter_files (files, &folders, &mountpoints, &err)) {
    g_warning (_("File filtering failed: %s"), err->message);
    g_error_free (err);
  } else {
    item = nautilus_menu_item_new (item_name,
                                   _("Wipe available disk space"),
                                   g_dngettext(GETTEXT_PACKAGE,
                                               "Wipe available disk space on "
                                               "this partition or device",
                                               "Wipe available disk space on "
                                               "these partitions or devices",
                                               g_list_length (mountpoints)),
                                   GTK_STOCK_CLEAR);
    g_object_set_data (G_OBJECT (item), ITEM_DATA_WINDOW_KEY, window);
    g_object_set_data_full (G_OBJECT (item), ITEM_DATA_PATHS_KEY,
                            folders,
                            (GDestroyNotify) nw_path_list_free);
    g_signal_connect (item, "activate",
                      G_CALLBACK (fill_menu_item_activate_handler), NULL);
    nw_path_list_free (mountpoints);
  }
  
  return item;
}

/* adds @item to the #GList @items if not %NULL */
#define ADD_ITEM(items, item)                         \
  G_STMT_START {                                      \
    NautilusMenuItem *ADD_ITEM__item = (item);        \
                                                      \
    if (ADD_ITEM__item != NULL) {                     \
      items = g_list_append (items, ADD_ITEM__item);  \
    }                                                 \
  } G_STMT_END

/* populates Nautilus' file menu */
static GList *
nw_extension_real_get_file_items (NautilusMenuProvider *provider,
                                  GtkWidget            *window,
                                  GList                *files)
{
  GList *items = NULL;
  GList *paths;
  
  paths = nw_path_list_new_from_nfi_list (files);
  if (paths) {
    ADD_ITEM (items, create_wipe_menu_item (provider,
                                            "nautilus-wipe::files-items::wipe",
                                            window, paths));
    ADD_ITEM (items, create_fill_menu_item (provider,
                                            "nautilus-wipe::files-items::fill",
                                            window, paths));
  }
  nw_path_list_free (paths);
  
  return items;
}

/* populates Nautilus' background menu */
static GList *
nw_extension_real_get_background_items (NautilusMenuProvider *provider,
                                        GtkWidget            *window,
                                        NautilusFileInfo     *current_folder)
{
  GList *items = NULL;
  GList *paths = NULL;
  
  paths = g_list_append (paths, nw_path_from_nfi (current_folder));
  if (paths && paths->data) {
    ADD_ITEM (items, create_fill_menu_item (provider,
                                            "nautilus-wipe::background-items::fill",
                                            window, paths));
  }
  nw_path_list_free (paths);
  
  return items;
}

#undef ADD_ITEM

#if defined(NW_NAUTILUS_IS_NAUTILUS_4)
static GList *
nw_extension_real_get_file_items_4 (NautilusMenuProvider *provider,
                                    GList                *files)
{
  return nw_extension_real_get_file_items (provider, NULL, files);
}
# define nw_extension_real_get_file_items nw_extension_real_get_file_items_4

static GList *
nw_extension_real_get_background_items_4 (NautilusMenuProvider *provider,
                                          NautilusFileInfo     *current_folder)
{
  return nw_extension_real_get_background_items (provider, NULL, current_folder);
}
# define nw_extension_real_get_background_items nw_extension_real_get_background_items_4
#endif /* defined(NW_NAUTILUS_IS_NAUTILUS_4) */

static void
nw_extension_menu_provider_iface_init (NautilusMenuProviderIface *iface)
{
  iface->get_file_items       = nw_extension_real_get_file_items;
  iface->get_background_items = nw_extension_real_get_background_items;
}

static void
nw_extension_class_init (NwExtensionClass *class)
{
}

static void
nw_extension_class_finalize (NwExtensionClass *class)
{
}

static void
nw_extension_init (NwExtension *self)
{
}
