/*
 * This file is part of GNAC - Gnome Audio Converter
 *
 * Copyright (C) 2007 - 2012 Gnac
 *    
 *    - DUPASQUIER  Benoit    <bdupasqu@src.gnome.org>
 *    - JOAQUIM     David     <djoaquim@src.gnome.org>
 *    - ROUX        Alexandre <alexroux@src.gnome.org>
 *
 * GNAC 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.
 *
 * GNAC 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 GNAC; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

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

#include <glib/gi18n.h>

#include "libgnac-converter.h"
#include "libgnac-error.h"
#include "libgnac-gst.h"
#include "libgnac-marshallers.h"
#include "libgnac-profile.h"


enum {
  PROP_0, 
  PROP_AUDIO_PIPELINE_DESC,
  PROP_MUXER_PIPELINE_DESC,
  PROP_VIDEO_PIPELINE_DESC,
  PROP_STRIP_SPECIAL,
  PROP_RENAME_PATTERN,
  PROP_FOLDER_HIERARCHY,
  PROP_FOLDER_TYPE,
  PROP_FOLDER_PATH,
  PROP_EXTENSION,
  PROP_NB_FILES
};

guint signals[LAST_SIGNAL] = { 0 };

struct LibgnacConverterPrivate
{
  GHashTable       *file_table;
  GQueue           *queue;
  GQueue           *queue_copy;
  LibgnacMediaItem *current;
  gint              n_converted;
  guint64           elapsed_duration;
  guint64           total_duration;

  gboolean          strip_special;
  LibgnacProfile   *profile;
  gchar            *rename_pattern;
  gint              folder_type;
  gchar            *folder_hierarchy;
  gchar            *folder_path;
  gchar            *extension;
};

G_DEFINE_TYPE(LibgnacConverter, libgnac_converter, G_TYPE_OBJECT);

#define LIBGNAC_CONVERTER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), \
    LIBGNAC_TYPE_CONVERTER, LibgnacConverterPrivate))


extern LibgnacMetadata *metadata;


static void
libgnac_converter_init(LibgnacConverter *self)
{
  LibgnacConverterPrivate *priv;

  self->priv = priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  priv->queue = g_queue_new();
  priv->queue_copy = NULL;

  priv->file_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
      (GDestroyNotify) libgnac_item_free);

  priv->profile = g_malloc(sizeof(LibgnacProfile));

  priv->profile->audio_desc = NULL;
  priv->profile->video_desc = NULL;
  priv->profile->muxer_desc = NULL;

  priv->current = NULL;
  priv->n_converted = -1;
  priv->total_duration = 0;
  priv->elapsed_duration = 0;

  priv->folder_path = NULL;
  priv->extension = NULL;
  priv->rename_pattern = NULL;
  priv->folder_hierarchy = NULL;
  priv->folder_type = 0;

  /* Init gstreamer plugins helper */
  gst_pb_utils_init();
}


static void
libgnac_converter_dispose(GObject *object)
{
  LibgnacConverter *self = LIBGNAC_CONVERTER(object);

  if (self->priv->file_table) {
    g_hash_table_unref(self->priv->file_table);
    self->priv->file_table = NULL;
  }

  if (self->priv->queue) {
    g_queue_free(self->priv->queue);
    self->priv->queue = NULL;
  }

  if (self->priv->queue_copy) {
    g_queue_free(self->priv->queue_copy);
    self->priv->queue_copy = NULL;
  }

  /* Chain up to the parent class */
  G_OBJECT_CLASS(libgnac_converter_parent_class)->dispose(object);
}


static void
libgnac_converter_finalize(GObject *object)
{
  LibgnacConverter *self = LIBGNAC_CONVERTER(object);

  g_free(self->priv->profile->audio_desc);
  g_free(self->priv->profile->muxer_desc);
  g_free(self->priv->profile->video_desc);
  g_free(self->priv->profile);
  g_free(self->priv->extension);
  g_free(self->priv->folder_path);
  g_free(self->priv->rename_pattern);
  g_free(self->priv->folder_hierarchy);

  libgnac_output_finalize();

  /* Chain up to the parent class */
  G_OBJECT_CLASS(libgnac_converter_parent_class)->finalize(object);
}


static void
libgnac_converter_get_property(GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *param)
{
  LibgnacConverter *self = LIBGNAC_CONVERTER(object);

  switch (property_id) 
  {
    case PROP_AUDIO_PIPELINE_DESC:
      g_value_set_string(value, self->priv->profile->audio_desc);
    break;

    case PROP_MUXER_PIPELINE_DESC:
      g_value_set_string(value, self->priv->profile->muxer_desc);
    break;

    case PROP_VIDEO_PIPELINE_DESC:
      g_value_set_string(value, self->priv->profile->video_desc);
    break;

    case PROP_STRIP_SPECIAL:
      g_value_set_boolean(value, self->priv->strip_special);
    break;

    case PROP_RENAME_PATTERN:
      g_value_set_string(value, self->priv->rename_pattern); 
    break;

    case PROP_FOLDER_HIERARCHY:
      g_value_set_string(value, self->priv->folder_hierarchy); 
    break;

    case PROP_FOLDER_TYPE:
      g_value_set_int(value, self->priv->folder_type); 
    break;

    case PROP_FOLDER_PATH:
      g_value_set_string(value, self->priv->folder_path); 
    break;

    case PROP_EXTENSION:
      g_value_set_string(value, self->priv->extension); 
    break;

    case PROP_NB_FILES:
      g_value_set_int(value, g_hash_table_size(self->priv->file_table));
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param);
    break;
  }
}


static void
libgnac_converter_set_property(GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *param)
{
  LibgnacConverter *self = LIBGNAC_CONVERTER(object);

  switch (property_id) 
  {
    case PROP_AUDIO_PIPELINE_DESC:
      g_free(self->priv->profile->audio_desc);
      self->priv->profile->audio_desc = g_value_dup_string(value);
    break;

    case PROP_MUXER_PIPELINE_DESC:
      g_free(self->priv->profile->muxer_desc);
      self->priv->profile->muxer_desc = g_value_dup_string(value);
    break;

    case PROP_VIDEO_PIPELINE_DESC:
      g_free(self->priv->profile->video_desc);
      self->priv->profile->video_desc = g_value_dup_string(value);
    break;

    case PROP_STRIP_SPECIAL:
      self->priv->strip_special = g_value_get_boolean(value);
    break;

    case PROP_RENAME_PATTERN:
      g_free(self->priv->rename_pattern);
      self->priv->rename_pattern = g_value_dup_string(value);
    break;

    case PROP_FOLDER_HIERARCHY:
      g_free(self->priv->folder_hierarchy);
      self->priv->folder_hierarchy = g_value_dup_string(value);
    break;

    case PROP_FOLDER_TYPE:
      self->priv->folder_type = g_value_get_int(value);
    break;

    case PROP_FOLDER_PATH:
      g_free(self->priv->folder_path);
      self->priv->folder_path = g_value_dup_string(value);
    break;

    case PROP_EXTENSION:
      g_free(self->priv->extension);
      self->priv->extension = g_value_dup_string(value);
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, param);
    break;
  }
}


static gboolean
libgnac_converter_allow_overwrite_accumulator(GSignalInvocationHint *ihint,
                                             GValue                *return_accu,
                                             const GValue          *handler_return,
                                             gpointer data)
{
  gboolean allow_overwrite = g_value_get_boolean(handler_return);
  g_value_set_boolean(return_accu, allow_overwrite);
  return FALSE;
}


static void
libgnac_converter_init_properties(GObjectClass *object_class)
{
  GParamSpec *param;

  /* Audio pipeline desc */
  param = g_param_spec_string("audio-description",
      "Audio pipeline description", "Audio pipeline description",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_AUDIO_PIPELINE_DESC, param);

  /* Muxer pipeline desc */
  param = g_param_spec_string("muxer-description",
      "Muxer pipeline description", "Muxer pipeline description",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_MUXER_PIPELINE_DESC, param);

  /* Video pipeline desc */
  param = g_param_spec_string("video-description",
      "Video pipeline description", "Video pipeline description",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_VIDEO_PIPELINE_DESC, param);

  /* Folder hierarchy */
  param = g_param_spec_string("folder-hierarchy",
      "Folder Hierarchy", "Folder hierarchy to create",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_FOLDER_HIERARCHY, param);

  /* Folder path */
  param = g_param_spec_string("folder-path",
      "Folder Path", "Path to the output folder",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_FOLDER_PATH, param);

  /* Extension */
  param = g_param_spec_string("extension",
      "File Extension", "Output's file extension",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_EXTENSION, param);

  /* Strip special characters */
  param = g_param_spec_boolean("strip-special",
      "Strip special", "Strip special characters",
      FALSE, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_STRIP_SPECIAL, param);

  /* Rename pattern */
  param = g_param_spec_string("rename-pattern",
      "Rename Pattern", "Output's rename pattern",
      NULL, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_RENAME_PATTERN, param);

  /* Folder type */
  param = g_param_spec_int("folder-type",
      "Folder type", "Output's folder type",
      0, FOLDER_TYPE_N, 0, G_PARAM_READWRITE);
  g_object_class_install_property(object_class, PROP_FOLDER_TYPE, param);

  /* Nb files */
  param = g_param_spec_int("nb-files",
      "File number", "Output's nb files",
      0, G_MAXINT, 0, G_PARAM_READABLE);
  g_object_class_install_property(object_class, PROP_NB_FILES, param);
}


static void
libgnac_converter_init_signals(GObjectClass *object_class)
{
  signals[OVERWRITE] = g_signal_new("allow-overwrite",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, overwrite),
      libgnac_converter_allow_overwrite_accumulator, NULL,
      g_cclosure_user_marshal_BOOLEAN__POINTER,
      G_TYPE_BOOLEAN, 1, G_TYPE_POINTER);

  signals[PROGRESS] = g_signal_new("progress",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, progress),
      NULL, NULL,
      g_cclosure_user_marshal_VOID__UINT_FLOAT_UINT64,
      G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_FLOAT, G_TYPE_UINT64);

  signals[FILE_ADDED] = g_signal_new("file-added",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, file_added),
      NULL, NULL,
      g_cclosure_marshal_VOID__STRING,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[FILE_REMOVED] = g_signal_new("file-removed",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, file_removed),
      NULL, NULL,
      g_cclosure_marshal_VOID__STRING,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[FILE_STARTED] = g_signal_new("file-started",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, file_started),
      NULL, NULL,
      g_cclosure_marshal_VOID__STRING,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[FILE_COMPLETED] = g_signal_new("file-completed",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, file_completed),
      NULL, NULL,
      g_cclosure_marshal_VOID__STRING,
      G_TYPE_NONE, 1, G_TYPE_STRING);

  signals[FILES_CLEARED] = g_signal_new("files-cleared",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, files_cleared),
      NULL, NULL,
      g_cclosure_marshal_VOID__VOID,
      G_TYPE_NONE, 0);

  signals[STARTED] = g_signal_new("started",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, started),
      NULL, NULL,
      g_cclosure_marshal_VOID__VOID,
      G_TYPE_NONE, 0);

  signals[STOPPED] = g_signal_new("stopped",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, stopped),
      NULL, NULL,
      g_cclosure_marshal_VOID__VOID,
      G_TYPE_NONE, 0);

  signals[COMPLETION] = g_signal_new("completion",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, completion),
      NULL, NULL,
      g_cclosure_marshal_VOID__VOID,
      G_TYPE_NONE, 0);

  signals[PLUGIN_INSTALL] = g_signal_new("plugin-install",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, plugin_install),
      NULL, NULL,
      g_cclosure_marshal_VOID__BOOLEAN,
      G_TYPE_NONE, 1, G_TYPE_BOOLEAN);

  signals[ERROR] = g_signal_new("error",
      G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST,
      G_STRUCT_OFFSET(LibgnacConverterClass, error),
      NULL, NULL,
      g_cclosure_user_marshal_VOID__STRING_STRING_POINTER,
      G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
}


static void
libgnac_converter_class_init(LibgnacConverterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS(klass);

  g_type_class_add_private(klass, sizeof(LibgnacConverterPrivate));

  object_class->dispose = libgnac_converter_dispose;
  object_class->finalize = libgnac_converter_finalize;

  object_class->set_property = libgnac_converter_set_property;
  object_class->get_property = libgnac_converter_get_property;

  libgnac_converter_init_properties(object_class);
  libgnac_converter_init_signals(object_class);
}


static gboolean
libgnac_converter_start_next(LibgnacConverter *self)
{
  g_return_val_if_fail(LIBGNAC_IS_CONVERTER(self), FALSE);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  GError *error = NULL;

  do {
    gchar *key = g_queue_pop_head(priv->queue_copy);
    if (!key) return FALSE;

    LibgnacMediaItem *item = g_hash_table_lookup(priv->file_table, key);
    if (!item) return FALSE;

    priv->n_converted++;
    priv->current = item;

    gchar *uri = g_file_get_uri(item->source);

    g_signal_emit(self, signals[FILE_STARTED], 0, uri);
    g_info("File started %s", uri);

    g_clear_error(&error);
    libgnac_item_run(item, &error);
    if (error)  {
      g_signal_emit(self, signals[ERROR], 0, uri, error->message, error);
    }

    LibgnacTags *tags = libgnac_metadata_extract(metadata, item->source, NULL);
    if (libgnac_metadata_tags_exist(tags, GST_TAG_DURATION, NULL)) {
      priv->elapsed_duration += g_value_get_uint64(
                      LIBGNAC_METADATA_TAG_DURATION(tags));
    }

    g_free(uri);

  } while (error);

  return TRUE;
}


static gboolean
libgnac_converter_restart_current(LibgnacConverter *self)
{
  g_return_val_if_fail(LIBGNAC_IS_CONVERTER(self), FALSE);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);
  if (!priv->current) return FALSE;

  g_info("Restarting current pipeline");

  libgnac_converter_emit_plugin_installed(self);

  GError *error = NULL;
  libgnac_item_run(priv->current, &error);
  if (error) {
    g_debug("%s", error->message);
    g_clear_error(&error);
    return FALSE;
  }

  return TRUE;
}


GObject *
libgnac_converter_new(LibgnacMetadata *mdata)
{
  metadata = mdata;
  return g_object_new(LIBGNAC_TYPE_CONVERTER, NULL);
}


LibgnacOutputConfig *
libgnac_converter_get_config(LibgnacConverter *self)
{
  LibgnacOutputConfig *config = g_malloc(sizeof(LibgnacOutputConfig));

  g_object_get(self, "extension", &(config->extension), NULL);
  g_object_get(self, "strip-special", &(config->strip_special), NULL);
  g_object_get(self, "folder-hierarchy", &(config->folder_hierarchy), NULL); 
  g_object_get(self, "folder-path", &(config->folder_path), NULL); 
  g_object_get(self, "rename-pattern", &(config->rename_pattern), NULL);
  g_object_get(self, "folder-type", &(config->folder_type), NULL);

  return config;
}


void
libgnac_converter_free_config(LibgnacOutputConfig *config)
{
  if (!config) return;

  g_free(config->extension);
  g_free(config->folder_hierarchy);
  g_free(config->folder_path);
  g_free(config->rename_pattern);
  g_free(config);
}


void
libgnac_converter_add(LibgnacConverter  *self, 
                      GFile             *file, 
                      GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  gchar *uri = g_file_get_uri(file);
  if (g_hash_table_lookup(priv->file_table, uri)) {
    g_set_error(error, LIBGNAC_ERROR, LIBGNAC_ERROR_SAME_FILE,
      _("File %s is already in the list"), uri);
    g_free(uri);
    return;
  }

  LibgnacMediaItem *item = libgnac_item_new(file, priv->profile, self);
  gchar *dup_uri = g_strdup(uri);
  // dup uri is freed within the hashtable
  g_queue_push_head(priv->queue, dup_uri);
  g_hash_table_insert(priv->file_table, dup_uri, item);
  g_signal_emit(self, signals[FILE_ADDED], 0, uri);

  LibgnacTags *tags = libgnac_metadata_extract(metadata, item->source, NULL);
  if (libgnac_metadata_tags_exist(tags, GST_TAG_DURATION, NULL)) {
    priv->total_duration += g_value_get_uint64(
                    LIBGNAC_METADATA_TAG_DURATION(tags));
  }

  g_free(uri);
}


void
libgnac_converter_remove(LibgnacConverter  *self, 
                         GFile             *file, 
                         GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);
  gchar *uri = g_file_get_uri(file);

  gpointer key_uri;
  gpointer item;

  if (!g_hash_table_lookup_extended(priv->file_table, uri, &key_uri, &item)) {
    g_warning("File not found %s", uri);
    g_set_error(error, LIBGNAC_ERROR, LIBGNAC_ERROR_MISSING_FILE,
        _("File %s is not in the list"), uri);
    g_free(uri);
    return;
  }

  GList *elem = g_queue_find(priv->queue, key_uri);
  if (!elem) {
    g_free(uri);
    return;
  }

  LibgnacTags *tags = libgnac_metadata_extract(metadata,
      ((LibgnacMediaItem *) item)->source, NULL);
  if (libgnac_metadata_tags_exist(tags, GST_TAG_DURATION, NULL)) {
    priv->total_duration -= g_value_get_uint64(
                  LIBGNAC_METADATA_TAG_DURATION(tags));
  }

  libgnac_metadata_remove(metadata, ((LibgnacMediaItem *) item)->source);
  g_queue_remove(priv->queue, elem->data);
  g_hash_table_remove(priv->file_table, uri);
  g_signal_emit(self, signals[FILE_REMOVED], 0, uri);

  g_free(uri);
}


void
libgnac_converter_clear(LibgnacConverter *self)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  g_queue_clear(priv->queue);
  g_hash_table_remove_all(priv->file_table);
  libgnac_metadata_remove_all(metadata);

  priv->total_duration = 0;

  g_signal_emit(self, signals[FILES_CLEARED], 0);
}


void
libgnac_converter_start(LibgnacConverter  *self, 
                        GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  if (priv->queue_copy) {
    g_queue_free(priv->queue_copy);
    priv->queue_copy = NULL;
  }

  priv->queue_copy = g_queue_copy(priv->queue);
  g_queue_sort(priv->queue_copy, (GCompareDataFunc) g_strcmp0, NULL);
  priv->n_converted = -1;
  priv->elapsed_duration = 0;

  g_signal_emit(self, signals[STARTED], 0);

  gchar *dbg_msg = g_strdup_printf(_("Encoding pipeline: %s"),
      priv->profile->audio_desc);
  g_info("%s", dbg_msg);
  g_free(dbg_msg);

  if (!libgnac_converter_start_next(self)) {
    g_signal_emit(self, signals[COMPLETION], 0);
  }
}


void
libgnac_converter_stop(LibgnacConverter  *self, 
                       GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);

  GError *err = NULL;
  libgnac_item_stop(priv->current, &err);
  if (err) {
    g_propagate_error(error, err);
    return;
  }

  priv->elapsed_duration = 0;
  priv->n_converted = -1;
  priv->current = NULL;
  g_signal_emit(self, signals[STOPPED], 0);
}


void
libgnac_converter_pause(LibgnacConverter  *self,
                        GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);
  libgnac_gst_pause(priv->current, error);
}


void
libgnac_converter_resume(LibgnacConverter  *self, 
                         GError           **error)
{
  g_return_if_fail(LIBGNAC_IS_CONVERTER(self));
  g_return_if_fail(!error || !*error);

  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(self);
  libgnac_gst_run(priv->current, error);
}


void
libgnac_converter_emit_plugin_install(LibgnacConverter *self)
{
  g_signal_emit(self, signals[PLUGIN_INSTALL], 0, FALSE);
}


void
libgnac_converter_emit_plugin_installed(LibgnacConverter *self)
{
  g_signal_emit(self, signals[PLUGIN_INSTALL], 0, TRUE);
}


void
libgnac_converter_eos_cb(GstBus     *bus, 
                         GstMessage *message, 
                         gpointer    data)
{
  LibgnacConverter *converter = (LibgnacConverter *) data;
  g_return_if_fail(LIBGNAC_IS_CONVERTER(converter));

  LibgnacMediaItem *item = converter->priv->current;
  gchar *uri = g_file_get_uri(item->source);
  g_signal_emit(converter, signals[FILE_COMPLETED], 0, uri);
  g_free(uri);

  /* Start next file conversion */
  if (!libgnac_converter_start_next(converter)) {
    converter->priv->n_converted = -1;
    g_signal_emit(converter, signals[COMPLETION], 0);
  }
}


static void
libgnac_converter_delete_file(GFile *file)
{
  if (!g_file_query_exists(file, NULL)) return;

  GError *error = NULL;
  g_file_delete(file, NULL, &error);
  if (error) {
    gchar *uri = g_file_get_uri(file);
    g_warning("Unable to remove file %s: %s", uri, error->message);
    g_free(uri);
    g_clear_error(&error);
  }
}


static gboolean
libgnac_converter_error_sink(LibgnacMediaItem *item, 
                             GstMessage *message, 
                             gchar      *uri, 
                             GError     *error)
{
  g_debug("Sink error");

  g_return_val_if_fail(item, FALSE);
  g_return_val_if_fail(item->destination, FALSE);
  g_return_val_if_fail(error, FALSE);

  /* Get parent folder name */
  GError *err = NULL;
  GFile *folder = g_file_get_parent(item->destination);
  gboolean folder_created = g_file_make_directory_with_parents(folder,
      NULL, &err);
  g_object_unref(folder);

  /* New folder create relaunch pipeline */
  if (folder_created && !err) {
    g_debug("Folder successfully created");
    return TRUE;
  }

  if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_EXISTS)) {
    g_debug("Unable to create directory");
    g_signal_emit(item->parent, signals[ERROR], 0, uri,
        _("Unable to create destination directory"), NULL);
    g_clear_error(&err);
    return FALSE;
  }

  g_clear_error(&err);

  if (libgnac_error_handle_dest_file_already_exists(item, message, uri)) {
    return FALSE;
  }

  return TRUE;
}


static gboolean
libgnac_converter_error_src(LibgnacMediaItem *item, 
                            GstMessage *message, 
                            gchar      *uri, 
                            GError     *error)
{
  g_debug("Source error");
  libgnac_converter_delete_file(item->destination);
  g_signal_emit(item->parent, signals[ERROR], 0, uri, 
      _("Unable to read source file"), error);
  return FALSE;
}


static gboolean
libgnac_converter_error_other(LibgnacMediaItem *item, 
                               GstMessage *message, 
                               gchar      *uri, 
                               GError     *error)
{
  g_debug("Other error");
  libgnac_converter_delete_file(item->destination);
  g_signal_emit(item->parent, signals[ERROR], 0, uri, 
      _("An error occured during conversion"), error);
  return FALSE;
}


void
libgnac_converter_error_cb(GstBus      *bus, 
                           GstMessage  *message, 
                           gpointer     data)
{
  LibgnacConverter *converter = (LibgnacConverter *) data;
  g_return_if_fail(LIBGNAC_IS_CONVERTER(converter));

  LibgnacMediaItem *item = converter->priv->current;

  gchar *uri = g_file_get_uri(item->source);
  gchar *out_uri = g_file_get_uri(item->destination);
  gchar *name = GST_MESSAGE_SRC_NAME(message);

  GError *error = NULL;
  gst_message_parse_error(message, &error, NULL);

  g_debug("An error has occured:\n"
                "\tFile: %s\n"
                "\tOut file %s\n"
                "\tBin name: %s\n"
                "\tError message: %s",
                uri, out_uri, name, error->message);

  gboolean restart = FALSE;

  if (g_str_has_prefix(name, "giosink")) {
    /* flush the bus to avoid receiving the same message twice */
    gst_bus_set_flushing(bus, TRUE);
    restart = libgnac_converter_error_sink(item, message, uri, error);
    /* restore the normal state of the bus */
    gst_bus_set_flushing(bus, FALSE);
  } else if (g_str_has_prefix(name, "giosrc")) {
    restart = libgnac_converter_error_src(item, message, uri, error);
  } else {
    restart = libgnac_converter_error_other(item, message, uri, error);
  }

  g_clear_error(&error);
  g_free(uri);
  g_free(out_uri);

  if (restart && libgnac_converter_restart_current(item->parent)) {
    /* The restart was ok, do not send any signal and do not start next*/
    return;
  }

  if (!libgnac_converter_start_next(converter)) {
    converter->priv->n_converted = -1;
    g_signal_emit(converter, signals[COMPLETION], 0);
  }     
}


void
libgnac_converter_missing_plugin_result_cb(GstInstallPluginsReturn result,
                                           gpointer                data)
{
  LibgnacConverter *converter = (LibgnacConverter *) data;
  g_return_if_fail(LIBGNAC_IS_CONVERTER(converter));

  LibgnacConverterPrivate *priv = converter->priv;
  LibgnacMediaItem *item = priv->current;

  if (result == GST_INSTALL_PLUGINS_SUCCESS ||
      result == GST_INSTALL_PLUGINS_PARTIAL_SUCCESS)
  {
    /* update the plugin registry */
    if (gst_update_registry()) {
      /* restart the conversion */
      if (libgnac_converter_restart_current(converter)) {
        return;
      }
    } else {
      g_debug("Failed to update GStreamer registry");
    }
  } else {
    gchar *uri = g_file_get_uri(item->source);
    g_signal_emit(converter, signals[ERROR], 0, uri, 
        _("Unable to handle this format"), NULL);
    g_free(uri);
    /* ask the user only once */
    if (result == GST_INSTALL_PLUGINS_USER_ABORT ||
        result == GST_INSTALL_PLUGINS_NOT_FOUND)
    {
      gchar *key;
      while ((key = g_queue_pop_head(priv->queue_copy))) {
        item = g_hash_table_lookup(priv->file_table, key);
        if (item) {
          gchar *uri = g_file_get_uri(item->source);
          g_signal_emit(converter, signals[ERROR], 0, uri,
              _("Unable to handle this format"), NULL);
          g_free(uri);
        }
      }
      g_signal_emit(converter, signals[COMPLETION], 0);
      return;
    }
  }

  if (!libgnac_converter_start_next(converter)) {
    converter->priv->n_converted = -1;
    g_signal_emit(converter, signals[COMPLETION], 0);
  }     
}


gboolean
libgnac_converter_percentage_cb(LibgnacConverter *converter)
{
  LibgnacConverterPrivate *priv = LIBGNAC_CONVERTER_GET_PRIVATE(converter);
  LibgnacMediaItem *item = priv->current;
  if (!item) return FALSE;

  if (!GST_IS_ELEMENT(item->pipeline)) {
    item->timeout_id = 0;
    return FALSE;
  }

  GstState state;
  GstState pending_state;
  gst_element_get_state(item->pipeline, &state, &pending_state, 0);

  if (state == GST_STATE_PAUSED) return TRUE;

  if (state != GST_STATE_PLAYING && pending_state != GST_STATE_PLAYING) {
    item->timeout_id = 0;
    return FALSE;
  }

  gint64 pos;
  static GstFormat format = GST_FORMAT_TIME;

  if (gst_element_query_position(item->pipeline, format, &pos)) {
    if (pos != item->pos) {
      gint64 len;
      gfloat result = 0.0f;
      guint64 time_left = 0;

      if (gst_element_query_duration(item->pipeline, format, &len)) {
        gfloat processed = priv->elapsed_duration + ((pos - len) / GST_SECOND);
        result = CLAMP(processed / priv->total_duration, 0.0f, 1.0f);
        /* XXX this is a workaround to prevent the remaining time
         * to get crazy when we have to deal with mp3wraped files */
        if (priv->total_duration > processed) {
          time_left = priv->total_duration - processed;
        }
      }

      g_signal_emit(converter, signals[PROGRESS], 0,
        priv->n_converted+1, result, time_left);
    }
  }

  return TRUE;
}
