/*
 * 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 <gio/gio.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include <stdlib.h>

#include "gnac-bars.h"
#include "gnac-file-list.h"
#include "gnac-main.h"
#include "gnac-options.h"
#include "gnac-playlist.h"
#include "gnac-prefs.h"
#include "gnac-properties.h"
#include "gnac-settings.h"
#include "gnac-ui.h"
#include "gnac-utils.h"

#include "libgnac-converter.h"
#include "libgnac-error.h"
#include "libgnac-metadata.h"

#include "profiles/gnac-profiles.h"
#include "profiles/gnac-profiles-utils.h"


static gboolean  conversion_errors = FALSE;
static gboolean  continue_files_action;
static gboolean  quit_app = FALSE;
static gboolean  remember_overwrite = FALSE;
static GThread  *file_action_thread = NULL;
static guint64   prev_time_left = -1;

guint nb_files_added;
guint nb_files_total;

LibgnacConverter *converter;
LibgnacMetadata  *metadata;

GnacState state = GNAC_AUDIO_EMPTY_STATE;
GnacState prev_state = GNAC_AUDIO_EMPTY_STATE;

static const gchar *states[] = {
  "GNAC_AUDIO_EMPTY_STATE",
  "GNAC_AUDIO_FILE_ACTION_STATE",
  "GNAC_AUDIO_READY_STATE",
  "GNAC_AUDIO_CLEAR_STATE",
  "GNAC_AUDIO_CONVERT_STATE",
  "GNAC_AUDIO_PAUSED_STATE",
  "GNAC_PLUGIN_INSTALL_STATE"
};


static void gnac_add(GFile *file);


static void
gnac_change_state(GnacState new_state)
{
  g_debug("State changed to %s", states[new_state]);

  prev_state = state;
  state = new_state;
  
  g_object_get(G_OBJECT(converter), "nb-files", &nb_files_total, NULL);

  gnac_file_list_update_cursor();

  switch (state) {
    case GNAC_AUDIO_EMPTY_STATE:
      gnac_ui_on_audio_empty_state();
      prev_time_left = -1;
      gnac_utils_moving_avg_reset();
      break;

    case GNAC_AUDIO_FILE_ACTION_STATE:
      gnac_ui_on_audio_file_action_state();
      break;

    case GNAC_AUDIO_READY_STATE: 
      remember_overwrite = FALSE;
      gnac_ui_on_audio_ready_state();
      prev_time_left = -1;
      gnac_utils_moving_avg_reset();
      break;

    case GNAC_AUDIO_CONVERT_STATE:
      gnac_ui_on_audio_convert_state();
      break;

    case GNAC_AUDIO_PAUSED_STATE:
      gnac_bars_on_convert_pause();
      break;

    case GNAC_PLUGIN_INSTALL_STATE:
      gnac_bars_on_plugin_install();
      break;

    case GNAC_AUDIO_CLEAR_STATE:
      break;

    default:
      /* nothing to do */
      break;
  }
}


static void
gnac_return_prev_state(void)
{
  gnac_change_state(prev_state);
}


static void
gnac_add_directory(GFile *file)
{
  GError *error = NULL;
  GFileEnumerator *enumerator = g_file_enumerate_children(file,
      G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_NAME,
      G_FILE_QUERY_INFO_NONE, NULL, &error);
  if (error) {
    g_debug("%s", error->message);
    g_clear_error(&error);
    return;
  }

  GFileInfo *info;
  while ((info = g_file_enumerator_next_file(enumerator, NULL, &error))) {
    GFile *child = g_file_get_child(file, g_file_info_get_name(info));
    gnac_add(child);
    g_object_unref(child);
    g_object_unref(info);
  }

  if (error) {
    g_debug("%s", error->message);
    g_clear_error(&error);
  }

  g_file_enumerator_close(enumerator, NULL, &error);
  if (error) {
    g_debug("%s", error->message);
    g_clear_error(&error);
  }

  g_object_unref(enumerator);
}


static void
gnac_add(GFile *file)
{
  if (!continue_files_action) return;

  switch (g_file_query_file_type(file, G_FILE_QUERY_INFO_NONE, NULL)) {
    case G_FILE_TYPE_REGULAR:
      gnac_add_file(file);
      break;
    case G_FILE_TYPE_DIRECTORY:
      gnac_add_directory(file);
      break;
    default:
      /* Nothing is done */
      break;
  }
}


static void
gnac_files_names_list_foreach(gpointer data, 
                              gpointer user_data)
{
  GFile *file = (GFile *) data;
  gnac_add(file);
  g_object_unref(file);
}


static void
gnac_add_files_thread_entry(gpointer data) 
{
  static GSList *files;

  /* If we are already adding files, simply update the list */
  if (files) {
    files = g_slist_concat(files, (GSList *) data);
    return;
  }

  gdk_threads_enter();
    gnac_file_list_detach_default_model();
    gnac_ui_push_status(_("Importing files..."));
  gdk_threads_leave();

  files = (GSList *) data;
  nb_files_added = 0;
  while (files) {
    gnac_files_names_list_foreach(files->data, NULL);
    files = g_slist_next(files);
  }
  g_slist_free(files);

  if (!quit_app) {
    gdk_threads_enter();
      gnac_ui_set_progress_text("");
      gnac_file_list_attach_default_model();
      gnac_change_state(GNAC_AUDIO_READY_STATE);
      gchar *files_added_text = g_strdup_printf(
          ngettext("%u file imported", "%u files imported", nb_files_added),
          nb_files_added);
      gnac_ui_push_status(files_added_text);
      g_free(files_added_text);
      file_action_thread = NULL;
    gdk_threads_leave();
  }
}


// The GList is freed by the thread itself.
void
gnac_add_files(GSList *files)
{
  if (!files) return;

  GError *error = NULL;

  continue_files_action = TRUE;
  gnac_change_state(GNAC_AUDIO_FILE_ACTION_STATE);
#if GLIB_CHECK_VERSION(2, 31, 0)
  file_action_thread = g_thread_try_new("add-files-thread",
      (GThreadFunc) gnac_add_files_thread_entry, files, &error);
#else
  file_action_thread = g_thread_create(
      (GThreadFunc) gnac_add_files_thread_entry, files, TRUE, &error);
#endif
  if (error) {
    g_warning(_("Failed to add files: %s"), error->message);
    g_clear_error(&error);
    gnac_return_prev_state();
  }
}


void
gnac_add_file(GFile *file)
{
  gchar *uri = g_file_get_uri(file);
  g_debug("Trying to add file %s", uri);

  gchar *mime_type = gnac_utils_get_mime_type(file);

  if (gnac_playlist_is_mime_playlist(mime_type)) {
    gnac_playlist_parse(file, mime_type);
  } else if (gnac_utils_file_format_is_supported(mime_type)) {
    GError *error = NULL;
    g_info("Add file %s", uri);
    libgnac_converter_add(converter, file, &error);
    if (error) {
      g_debug("Failed to add file %s: %s", uri, error->message);
      g_clear_error(&error);
    }
  }

  g_free(uri);
  g_free(mime_type);
}


G_GNUC_NORETURN void
gnac_exit(gint status)
{
  gnac_on_ui_destroy_cb(NULL, NULL);
  exit(status);
}


gboolean
gnac_on_ui_destroy_cb(GtkWidget *widget, 
                      gpointer   data)
{
  switch (state) {
    /* Do not allow the destruction of the main window during
     * the installation of a plugin */
    case GNAC_PLUGIN_INSTALL_STATE:
      return TRUE;

    case GNAC_AUDIO_CONVERT_STATE:
    case GNAC_AUDIO_PAUSED_STATE: {
      if (!gnac_ui_confirm_exit()) return TRUE;
      GError *error = NULL;
      libgnac_converter_stop(converter, &error);
      if (error) {
        g_debug("Error: %s", error->message);
        g_clear_error(&error);
      }
      break;
    }

    case GNAC_AUDIO_FILE_ACTION_STATE: {
      if (file_action_thread) {
        quit_app = TRUE;
        continue_files_action = FALSE;
        g_thread_join(file_action_thread);
        gnac_change_state(GNAC_AUDIO_READY_STATE);
      }
    }

    case GNAC_AUDIO_EMPTY_STATE:
    case GNAC_AUDIO_READY_STATE:
    case GNAC_AUDIO_CLEAR_STATE:
      // nothing particular to do
      break;

  }

  if (G_IS_OBJECT(converter)) g_object_unref(converter);
  if (G_IS_OBJECT(metadata)) g_object_unref(metadata);

  gnac_ui_destroy();

  if (gtk_main_level() != 0) gtk_main_quit();

  return FALSE;
}


static gboolean
gnac_on_converter_overwrite_cb(LibgnacConverter *converter, 
                               GFile            *file,
                               gpointer          user_data)
{

  static gboolean overwrite = FALSE;

  if (remember_overwrite) return overwrite;

  GError *error = NULL;
  gchar *display_name = gnac_utils_get_display_name_from_file(file, &error);

  GtkWidget *dialog = gtk_message_dialog_new(
      GTK_WINDOW(gnac_ui_get_widget("main_window")),
      GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_YES_NO,
      _("File %s already exists...\nOverwrite?"),
      display_name);
  g_free(display_name);
  gtk_window_set_title(GTK_WINDOW(dialog), PACKAGE_NAME);

  GtkWidget *checkbox = gtk_check_button_new_with_label(
      _("Remember my decision"));
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbox), FALSE);
  gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
      checkbox, FALSE, FALSE, 0);
  gtk_widget_show(checkbox);

  gdk_threads_enter();
    gint response = gtk_dialog_run(GTK_DIALOG(dialog));
  gdk_threads_leave();

  remember_overwrite = gtk_toggle_button_get_active(
      GTK_TOGGLE_BUTTON(checkbox));

  gtk_widget_destroy(dialog);

  overwrite = (response == GTK_RESPONSE_YES);

  return overwrite;
}


static void
gnac_on_converter_progress_cb(LibgnacConverter *converter, 
                              guint             nb_converted,
                              gfloat            fraction,
                              guint64           time_left)
{
  if (state == GNAC_AUDIO_CONVERT_STATE) {
    gchar *msg = g_strdup_printf(_("Converting file %d of %d"),
        nb_converted, nb_files_total);
    gnac_ui_push_status(msg);
    g_free(msg);
    gnac_ui_set_progress_fraction(fraction);

    if (prev_time_left != -1) {
      guint64 progress = prev_time_left - time_left;
      if (progress <= 0) return;
      
      guint64 left = ((time_left / progress) * PROGRESS_TIMEOUT) / 1000;
      gchar *time_str = gnac_utils_format_duration_for_display(
          gnac_utils_moving_avg_add_sample(left));
      /* Translators: time left (the format for time is hours:minutes:seconds) */
      gchar *time_str_label = g_strdup_printf(_("%s left"), time_str);

      gnac_ui_set_progress_text(time_str_label);

      g_free(time_str);
      g_free(time_str_label);
    }

    prev_time_left = time_left;
  }
}


static void
gnac_on_converter_file_added_cb(LibgnacConverter *converter,
                                const gchar      *uri)
{
  nb_files_added++;
  gnac_file_list_add_row(uri);
}


static void
gnac_on_converter_file_removed_cb(LibgnacConverter *converter,
                                  const gchar      *uri)
{
  gnac_file_list_remove_row(uri);
  if (gnac_file_list_count_rows() == 0) {
    gnac_change_state(GNAC_AUDIO_EMPTY_STATE);
  }
}


static void
gnac_on_converter_file_started_cb(LibgnacConverter *converter,
                                  const gchar      *input)
{
  gnac_file_list_remove_visual_error(input);
  gnac_file_list_select_uri(input);
  gnac_ui_trayicon_tooltip_update(input);
}


static void
gnac_trash_file(GFile *file)
{
  GError *error = NULL;

  g_file_trash(file, NULL, &error);
  if (!error ||
      !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
  {
    g_clear_error(&error);
    return;
  }

  g_clear_error(&error);
  gchar *filename = gnac_utils_get_display_name_from_file(file, &error);
  if (error) {
    g_clear_error(&error);
    g_object_unref(file);
    return;
  }

  gint response = gnac_ui_show_error_trash(filename);

  g_free(filename);

  switch (response) {
    case 1:
      g_file_delete(file, NULL, &error);
      if (error) {
        g_printerr("%s: %s\n", _("Error"), error->message);
        g_clear_error(&error);
      }
      break;
    default:
      /* do nothing */
      break;
  }
}


static void
gnac_on_converter_file_completed_cb(LibgnacConverter *converter,
                                    const gchar      *uri)
{
  /* if delete source is selected, there is nothing else to do */
  if (!gnac_settings_get_boolean(GNAC_KEY_CLEAR_SOURCE)) return;

  GFile *file = g_file_new_for_uri(uri);
  gnac_trash_file(file);
  g_object_unref(file);
}


static void
gnac_on_converter_files_cleared_cb(LibgnacConverter *converter)
{
  gnac_file_list_remove_all();
  gnac_change_state(GNAC_AUDIO_EMPTY_STATE);
  gnac_ui_push_status("");
}


static void
gnac_on_converter_completion_cb(LibgnacConverter *converter)
{
  gnac_change_state(GNAC_AUDIO_READY_STATE);

  const gchar *msg;

  if (conversion_errors) {
    msg = _("Conversion completed with errors");
  } else {
    msg = _("Conversion completed");
  }

  gnac_ui_on_conversion_completed(msg);
  g_debug("%s", msg);
}


static void
gnac_on_converter_started_cb(LibgnacConverter *converter)
{
  gnac_change_state(GNAC_AUDIO_CONVERT_STATE);
  conversion_errors = FALSE;
}


static void
gnac_on_converter_stopped_cb(LibgnacConverter *converter)
{
  gnac_change_state(GNAC_AUDIO_READY_STATE);
  gnac_ui_push_status(_("Conversion stopped"));
}


static void
gnac_on_converter_plugin_install_cb(LibgnacConverter *converter,
                                    gboolean          installed)
{
  if (installed) {
    gnac_bars_on_plugin_installed();
    gnac_change_state(GNAC_AUDIO_CONVERT_STATE);
  } else {
    gnac_change_state(GNAC_PLUGIN_INSTALL_STATE);
    gnac_ui_push_status(_("Codec installer started"));
  }
}


static void
gnac_on_converter_error_cb(LibgnacConverter *converter,
                           const gchar      *uri,
                           const gchar      *msg,
                           GError           *error)
{
  if (g_error_matches(error, LIBGNAC_ERROR, LIBGNAC_ERROR_FILE_EXISTS)) {
    gchar *text = g_strdup_printf("%s (%s)", _("File not converted"), msg);
    gnac_file_list_visual_error(uri, text, GTK_STOCK_DIALOG_INFO);
    g_free(text);
  } else {
    conversion_errors = TRUE;
    gnac_file_list_visual_error(uri, msg, NULL);
    g_printerr("%s %s\n %s: %s\n", 
        _("Failed to convert file"), uri, _("Error"), msg);
  }
}


/* UI Callbacks */

void
gnac_on_ui_pause_cb(GtkWidget *widget,
                    gpointer   data)
{
  GError *error = NULL;

  if (state == GNAC_AUDIO_CONVERT_STATE) {
    gnac_change_state(GNAC_AUDIO_PAUSED_STATE);
    libgnac_converter_pause(converter, &error);
  } else {
    gnac_change_state(GNAC_AUDIO_CONVERT_STATE);
    libgnac_converter_resume(converter, &error);
  }

  g_clear_error(&error);
}


static void
gnac_start_conversion(GError **error)
{
  gchar *folder_hierarchy = gnac_settings_get_string(
      GNAC_KEY_FOLDER_HIERARCHY_PATTERN);
  gchar *folder_path = gnac_settings_get_string(
      GNAC_KEY_DESTINATION_DIRECTORY);
  gchar *rename_pattern = gnac_settings_get_string(
      GNAC_KEY_RENAME_PATTERN_PATTERN);
  g_object_set(G_OBJECT(converter),
      "extension", gnac_profiles_get_extension(),
      "folder-hierarchy", folder_hierarchy,
      "folder-path", folder_path,
      "folder-type", gnac_settings_get_int(GNAC_KEY_FOLDER_TYPE),
      "audio-description", gnac_profiles_get_pipeline(),
      "rename-pattern", rename_pattern,
      "strip-special", gnac_settings_get_boolean(GNAC_KEY_STRIP_SPECIAL),
      NULL);

  libgnac_converter_start(converter, error);

  g_free(folder_path);
  g_free(folder_hierarchy);
  g_free(rename_pattern);
}


void
gnac_on_ui_convert_cb(GtkWidget *widget, 
                      gpointer   data)
{
  GError *error = NULL;

  switch (state) {
    case GNAC_AUDIO_CONVERT_STATE:
    case GNAC_AUDIO_PAUSED_STATE:
      libgnac_converter_stop(converter, &error);
      break;

    case GNAC_AUDIO_READY_STATE:
      gnac_start_conversion(&error);
      break;

    case GNAC_AUDIO_FILE_ACTION_STATE:
      continue_files_action = FALSE;
      break;

    default:
      // uha we have a problem
      g_return_if_reached();
      break;
  }

  if (error) {
    g_debug("Error: %s", error->message);
    g_clear_error(&error);
  }
}


static void
gnac_init_libgnac(void)
{
  metadata = LIBGNAC_METADATA(libgnac_metadata_new());
  converter = LIBGNAC_CONVERTER(libgnac_converter_new(metadata));

  g_signal_connect(converter, "allow-overwrite",
      G_CALLBACK(gnac_on_converter_overwrite_cb), converter);
  g_signal_connect(converter, "completion",
      G_CALLBACK(gnac_on_converter_completion_cb), converter);
  g_signal_connect(converter, "error",
      G_CALLBACK(gnac_on_converter_error_cb), converter);
  g_signal_connect(converter, "file-added",
      G_CALLBACK(gnac_on_converter_file_added_cb), converter);
  g_signal_connect(converter, "files-cleared",
      G_CALLBACK(gnac_on_converter_files_cleared_cb), converter);
  g_signal_connect(converter, "file-completed",
      G_CALLBACK(gnac_on_converter_file_completed_cb), converter);
  g_signal_connect(converter, "file-removed",
      G_CALLBACK(gnac_on_converter_file_removed_cb), converter);
  g_signal_connect(converter, "file-started",
      G_CALLBACK(gnac_on_converter_file_started_cb), converter);
  g_signal_connect(converter, "plugin-install",
      G_CALLBACK(gnac_on_converter_plugin_install_cb), converter);
  g_signal_connect(converter, "progress",
      G_CALLBACK(gnac_on_converter_progress_cb), converter);
  g_signal_connect(converter, "started",
      G_CALLBACK(gnac_on_converter_started_cb), converter);
  g_signal_connect(converter, "stopped",
      G_CALLBACK(gnac_on_converter_stopped_cb), converter);
}


gint
main(gint    argc,
     gchar **argv)
{
#if !GLIB_CHECK_VERSION(2, 31, 0)
  if (!g_thread_supported()) g_thread_init(NULL);
#endif

  g_type_init();

  #ifdef ENABLE_NLS
    bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    textdomain(GETTEXT_PACKAGE);
  #endif

  g_set_application_name(PACKAGE_NAME);

  gnac_settings_init();

  /* Parse command line arguments */
  gnac_options_init(argc, argv);

  gdk_threads_init();

  gnac_ui_init();
  gnac_init_libgnac();

  gnac_ui_show();

  /* Check if arguments have been passed through the command line */
  gnac_options_process_late();

  gdk_threads_enter();
    gtk_main();
  gdk_threads_leave();

  return EXIT_SUCCESS;
}
