/*
 *  nautilus-wipe - a nautilus extension to wipe file(s)
 * 
 *  Copyright (C) 2009-2012 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-fill-operation.h"

#include <glib.h>
#include <glib/gi18n-lib.h>
#include <glib-object.h>
#include <gsecuredelete.h>

#include "nw-operation.h"


static void     nw_fill_operation_operation_iface_init    (NwOperationInterface *iface);
static void     nw_fill_operation_real_add_file           (NwOperation *op,
                                                           const gchar *path);
static gchar   *nw_fill_operation_real_get_progress_step  (NwOperation *op);
static void     nw_fill_operation_finalize                (GObject *object);
static void     nw_fill_operation_finished_handler        (GsdFillOperation *operation,
                                                           gboolean          success,
                                                           const gchar      *message,
                                                           NwFillOperation  *self);
static void     nw_fill_operation_progress_handler        (GsdFillOperation *operation,
                                                           gdouble           fraction,
                                                           NwFillOperation  *self);


struct _NwFillOperationPrivate {
  GList    *directories;
  
  guint     n_op;
  guint     n_op_done;
  GString  *message;
  
  gulong    progress_hid;
  gulong    finished_hid;
};

G_DEFINE_TYPE_WITH_CODE (NwFillOperation,
                         nw_fill_operation,
                         GSD_TYPE_FILL_OPERATION,
                         G_IMPLEMENT_INTERFACE (NW_TYPE_OPERATION,
                                                nw_fill_operation_operation_iface_init))


static void
nw_fill_operation_operation_iface_init (NwOperationInterface *iface)
{
  iface->add_file           = nw_fill_operation_real_add_file;
  iface->get_progress_step  = nw_fill_operation_real_get_progress_step;
}

static void
nw_fill_operation_class_init (NwFillOperationClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  
  object_class->finalize = nw_fill_operation_finalize;
  
  g_type_class_add_private (klass, sizeof (NwFillOperationPrivate));
}

static void
nw_fill_operation_init (NwFillOperation *self)
{
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
                                            NW_TYPE_FILL_OPERATION,
                                            NwFillOperationPrivate);
  
  self->priv->directories = NULL;
  self->priv->n_op = 0;
  self->priv->n_op_done = 0;
  self->priv->message = NULL;
  
  self->priv->finished_hid = g_signal_connect (self, "finished",
                                               G_CALLBACK (nw_fill_operation_finished_handler),
                                               self);
  self->priv->progress_hid = g_signal_connect (self, "progress",
                                               G_CALLBACK (nw_fill_operation_progress_handler),
                                               self);
}

static void
nw_fill_operation_finalize (GObject *object)
{
  NwFillOperation *self = NW_FILL_OPERATION (object);
  
  g_list_foreach (self->priv->directories, (GFunc) g_free, NULL);
  g_list_free (self->priv->directories);
  self->priv->directories = NULL;
  
  if (self->priv->message) {
    g_string_free (self->priv->message, TRUE);
    self->priv->message = NULL;
  }
  
  G_OBJECT_CLASS (nw_fill_operation_parent_class)->finalize (object);
}

static void
nw_fill_operation_real_add_file (NwOperation *op,
                                 const gchar *path)
{
  NwFillOperation *self = NW_FILL_OPERATION (op);
  
  /* FIXME: filter file? */
  self->priv->directories = g_list_prepend (self->priv->directories,
                                            g_strdup (path));
  self->priv->n_op ++;
  
  gsd_fill_operation_set_directory (GSD_FILL_OPERATION (self),
                                    self->priv->directories->data);
}

static gchar *
nw_fill_operation_real_get_progress_step (NwOperation *operation)
{
  NwFillOperation    *self  = NW_FILL_OPERATION (operation);
  GsdAsyncOperation  *op    = GSD_ASYNC_OPERATION (operation);
  
  if (self->priv->n_op > 1) {
    return g_strdup_printf (_("Device \"%s\" (%u out of %u), pass %u out of %u"),
                            (const gchar *) self->priv->directories->data,
                            self->priv->n_op_done + 1, self->priv->n_op,
                            op->passes + 1, op->n_passes);
  } else {
    return g_strdup_printf (_("Device \"%s\", pass %u out of %u"),
                            (const gchar *) self->priv->directories->data,
                            op->passes + 1, op->n_passes);
  }
}

/* wrapper for the progress handler returning the current progression over all
 * operations  */
static void
nw_fill_operation_progress_handler (GsdFillOperation *operation,
                                    gdouble           fraction,
                                    NwFillOperation  *self)
{
  /* abort emission and replace by our overridden one.  Not to do that
   * recursively, we block our handler during the re-emission */
  g_signal_stop_emission_by_name (operation, "progress");
  g_signal_handler_block (operation, self->priv->progress_hid);
  g_signal_emit_by_name (operation, "progress",
                         (self->priv->n_op_done + fraction) / self->priv->n_op);
  g_signal_handler_unblock (operation, self->priv->progress_hid);
}

static void
append_error_message (NwFillOperation  *self,
                      const gchar      *message)
{
  if (! self->priv->message) {
    self->priv->message = g_string_new (message);
  } else {
    g_string_append (self->priv->message, "\n");
    g_string_append (self->priv->message, message);
  }
}

static void
emit_final_finished (NwFillOperation *self,
                     gboolean         success,
                     const gchar     *message)
{
  const gchar *full_message;
  
  if (! self->priv->message) {
    full_message = message;
  } else {
    append_error_message (self, message);
    full_message = self->priv->message->str;
  }
  
  g_signal_handler_block (self, self->priv->finished_hid);
  g_signal_emit_by_name (self, "finished", success, full_message);
  g_signal_handler_unblock (self, self->priv->finished_hid);
}

/* timeout function to launch next operation after finish of the previous
 * operation.
 * we need this kind of hack since operation are locked while running. */
static gboolean
launch_next_operation (NwFillOperation *self)
{
  gboolean busy;
  
  busy = gsd_async_operation_get_busy (GSD_ASYNC_OPERATION (self));
  if (! busy) {
    GError   *err = NULL;
    gboolean  success;
    
    success = gsd_fill_operation_run (GSD_FILL_OPERATION (self),
                                      self->priv->directories->data,
                                      &err);
    if (! success) {
      emit_final_finished (self, success, err->message);
      g_error_free (err);
    } else {
      /* as the step changed, report the progress changed */
      g_signal_emit_by_name (self, "progress", 0.0);
    }
  }
  
  return busy; /* keeps our timeout function until lock is released */
}

/* Removes the current directory to proceed */
static void
nw_fill_operation_pop_dir (NwFillOperation *self)
{
  GList *tmp;
  
  tmp = self->priv->directories;
  if (tmp) {
    self->priv->directories = tmp->next;
    g_free (tmp->data);
    g_list_free_1 (tmp);
  }
}

/* Wrapper for the finished handler.  It launches the next operation if there
 * is one left. */
static void
nw_fill_operation_finished_handler (GsdFillOperation *operation,
                                    gboolean          success,
                                    const gchar      *message,
                                    NwFillOperation  *self)
{
  gboolean last = TRUE;
  
  if (success) {
    self->priv->n_op_done++;
    /* remove the directory just proceeded */
    nw_fill_operation_pop_dir (self);
    
    /* if we have work left to be done... */
    if (self->priv->directories) {
      /* block signal emission, it's not the last one */
      g_signal_stop_emission_by_name (operation, "finished");
      
      /* remember any warning for the final signal */
      if (message) {
        append_error_message (self, message);
      }
      
      /* we can't launch the next operation right here since the previous must
       * release its lock before, which is done just after return of the current
       * function.
       * To work around this, we add a timeout function that will try to launch
       * the next operation if the current one is not busy, which fixes the
       * problem. */
      g_timeout_add (10, (GSourceFunc) launch_next_operation, self);
      last = FALSE;
    }
  }
  /* if we didn't schedule a new job, check if we have to alter the signal */
  if (last && self->priv->message) {
    g_signal_stop_emission_by_name (operation, "finished");
    emit_final_finished (self, success, message);
  }
}

NwOperation *
nw_fill_operation_new (void)
{
  return g_object_new (NW_TYPE_FILL_OPERATION, NULL);
}
