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

#include "formats/gnac-profiles-aac.h"
#include "formats/gnac-profiles-flac.h"
#include "formats/gnac-profiles-lame.h"
#include "formats/gnac-profiles-opus.h"
#include "formats/gnac-profiles-speex.h"
#include "formats/gnac-profiles-unknown.h"
#include "formats/gnac-profiles-vorbis.h"
#include "formats/gnac-profiles-wav.h"
#include "formats/gnac-profiles-wavpack.h"

#include "gnac-main.h"
#include "gnac-profiles-default.h"
#include "gnac-profiles-properties.h"
#include "gnac-ui-utils.h"
#include "gnac-utils.h"



enum {
  COL_NAME,
  COL_MODULE,
  COL_SORT,
  NUM_COLS
};

static const guint forbidden_chars_keys[] = { GDK_KEY_slash };
static const gchar forbidden_chars[] = { '/', '\n', '\t' };
static const gchar *forbidden_chars_string = "   /, <return>, <tab>";

static FormatModuleGetFuncs formats_get_funcs[] = {
  gnac_profiles_aac_get_funcs,
  gnac_profiles_flac_get_funcs,
  gnac_profiles_lame_get_funcs,
  gnac_profiles_opus_get_funcs,
  gnac_profiles_speex_get_funcs,
  gnac_profiles_unknown_get_funcs,
  gnac_profiles_vorbis_get_funcs,
  gnac_profiles_wav_get_funcs,
  gnac_profiles_wavpack_get_funcs
};

typedef struct {
  FormatModuleFuncs    funcs;
  GtkTreeRowReference *tree_ref;
} FormatModule;

static GHashTable *formats;

static FormatModule *current_format_module;
static GtkBuilder   *profiles_properties_builder = NULL;
static const gchar  *current_profile_name;

static void
gnac_profiles_properties_init_format(void);

static void
gnac_profiles_properties_display_status_message(const gchar *error);

static gboolean
gnac_profiles_properties_is_valid_profile_name(const gchar *name);

static gboolean
gnac_profiles_properties_is_valid_filename_chars(const gchar *chars);

static gboolean
gnac_profiles_properties_is_valid_filename_char(guint keyval);

static void
gnac_profiles_properties_on_save(GtkWidget *widget,
                                 gpointer   data);

static void
gnac_profiles_properties_name_description_set_text(const gchar *name,
                                                   const gchar *description);

static void
gnac_profiles_properties_reset(void);


static GObject *
gnac_profiles_properties_get_object(const gchar *object_name)
{
  return gnac_ui_utils_get_object(profiles_properties_builder, object_name);
}


static GtkWidget *
gnac_profiles_properties_get_widget(const gchar *widget_name)
{
  return gnac_ui_utils_get_widget(profiles_properties_builder, widget_name);
}


void
gnac_profiles_properties_init(void)
{
  formats = g_hash_table_new(g_str_hash, g_str_equal);
  saved_profiles_dir = g_build_filename(g_get_user_data_dir(),
      "gnac", "profiles", NULL);

  profiles_properties_builder = gnac_ui_utils_create_gtk_builder(
      PKGDATADIR "/profiles/gnac-profiles-properties.xml");

  gnac_profiles_properties_init_format();
  gnac_profiles_properties_display_status_message(NULL);
}


void
gnac_profiles_properties_set_parent(GtkWindow *parent)
{
  GtkWindow *window = GTK_WINDOW(gnac_profiles_properties_get_widget("window1"));
  gtk_window_set_transient_for(window, parent);
}


static void
gnac_profiles_properties_init_format(void)
{
  GtkListStore *model = GTK_LIST_STORE(
      gnac_profiles_properties_get_object("liststore1"));
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(model), COL_SORT,
      GTK_SORT_ASCENDING);

  guint i;
  GtkTreeIter iter;

  for (i = 0; i < G_N_ELEMENTS(formats_get_funcs); i++) {
    FormatModule *format_module = g_malloc(sizeof(FormatModule));
    format_module->funcs = formats_get_funcs[i]();
    const gchar *format_id = format_module->funcs.init();
    gchar *format_name = format_module->funcs.get_combo_format_name();

    g_hash_table_insert(formats, (gpointer) format_id, format_module);
    gtk_list_store_append(model, &iter);

    // XXX hack to ensure 'Custom Format' is last in the combo
    const gchar *sort_key = gnac_utils_str_equal(format_id, "custom-format") ?
        "ZZZZZZZZZZZZZZZZZZZ" : format_name;

    gtk_list_store_set(model, &iter,
        COL_NAME, format_name,
        COL_MODULE, format_module,
        COL_SORT, sort_key,
        -1);

    g_free(format_name);

    GtkTreePath *tree_path = gtk_tree_model_get_path(
        GTK_TREE_MODEL(model), &iter);
    format_module->tree_ref = gtk_tree_row_reference_new(
        GTK_TREE_MODEL(model), tree_path);
    gtk_tree_path_free(tree_path);
    
    if (format_module->funcs.set_pipeline_func) {
      GtkWidget *widget =
          gnac_profiles_properties_get_widget("gstreamer_pipeline_text_view");
      GtkWidget *pipeline_box =
          gnac_profiles_properties_get_widget("gstreamer_pipeline_box");
      format_module->funcs.set_pipeline_func(GTK_TEXT_VIEW(widget),
          pipeline_box);
    }

    GtkWidget *hbox_properties = gnac_profiles_properties_get_widget(
        "hbox_properties");
    GtkWidget *properties = format_module->funcs.get_widget();
    gtk_box_pack_start(GTK_BOX(hbox_properties), properties, TRUE, TRUE, 0);
    gtk_widget_hide(properties);
  }
}


void
gnac_profiles_properties_show(gpointer     profile,
                              const gchar *title,
                              GCallback    call_back)
{
  gnac_profiles_properties_reset();

  GtkWidget *widget = gnac_profiles_properties_get_widget("save-button");

  g_signal_connect(G_OBJECT(widget), "clicked",
      G_CALLBACK(gnac_profiles_properties_on_save), call_back);
  
  widget = gnac_profiles_properties_get_widget("window1");
  gtk_window_set_title(GTK_WINDOW(widget), title);

  GtkWidget *format_combo_box =
      gnac_profiles_properties_get_widget("format_combo_box");

  if (!profile) {
    gtk_widget_show(widget);
    gtk_combo_box_set_active(GTK_COMBO_BOX(format_combo_box), 0);
    return;
  }

  AudioProfileGeneric *generic = AUDIO_PROFILE_GET_GENERIC(profile);
  const gchar *format_id = generic->format_id;
  if (!format_id) return;

  FormatModule *format_module = g_hash_table_lookup(formats, format_id);
  if (!format_module) {
    g_warning(_("Format not supported"));
    return;
  }

  GtkTreeModel *model = gtk_combo_box_get_model(GTK_COMBO_BOX(format_combo_box));
  gnac_profiles_properties_name_description_set_text(generic->name,
      generic->description);
  format_module->funcs.set_fields(profile);

  GtkTreeIter iter;
  GtkTreePath *path = gtk_tree_row_reference_get_path(format_module->tree_ref);
  gtk_tree_model_get_iter(model, &iter, path);
  gtk_tree_path_free(path);
  gtk_combo_box_set_active_iter(GTK_COMBO_BOX(format_combo_box), &iter);
  format_module->funcs.generate_pipeline();

  gtk_widget_show(widget);
}


void
gnac_profiles_properties_free_audio_profile(gpointer profile)
{
  if (!profile) return;

  AudioProfileGeneric *generic = AUDIO_PROFILE_GET_GENERIC(profile);
  const gchar *format_id = generic->format_id;
  if (!format_id) return;

  FormatModule *format_module = g_hash_table_lookup(formats, format_id);
  if (!format_module) {
    g_warning(_("Format not supported"));
    return;
  }

  format_module->funcs.free_audio_profile(profile);
}


void
gnac_profiles_properties_destroy(void)
{
  if (!formats) return;

  GList *temp;
  GList *list = g_hash_table_get_values(formats);

  for (temp = list; temp; temp = g_list_next(temp)) {
    FormatModule *format_module = (FormatModule *) temp->data;
    format_module->funcs.clean_up();
    g_free(format_module);
  }

  g_list_free(list);
  g_hash_table_destroy(formats);
  formats = NULL;
  g_free(saved_profiles_dir);

  GtkWidget *window = gnac_profiles_properties_get_widget("window1");
  gtk_widget_destroy(window);
  g_object_unref(profiles_properties_builder);
}


void
gnac_profiles_properties_combo_format_on_changed(GtkComboBox *combo,
                                                 gpointer     user_data)
{
  GtkTreeModel *model = gtk_combo_box_get_model(combo);

  GtkTreeIter iter;
  if (!gtk_combo_box_get_active_iter(combo, &iter)) return;

  FormatModule *format_module;
  gtk_tree_model_get(model, &iter, 1, &format_module, -1);

  gnac_profiles_properties_display_status_message(NULL);

  if (current_format_module && format_module != current_format_module) {
    GtkWidget *properties = current_format_module->funcs.get_widget();
    gtk_widget_hide(properties);
  }

  GtkWidget *text_view =
      gnac_profiles_properties_get_widget("gstreamer_pipeline_text_view");
  gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE);

  #ifndef GNOME_ENABLE_DEBUG
    text_view = gnac_profiles_properties_get_widget("gstreamer_pipeline_box");
    gtk_widget_hide(text_view);
  #endif

  GtkLabel *label = GTK_LABEL(
      gnac_profiles_properties_get_widget("format_description_label"));
  gtk_label_set_text(label, format_module->funcs.get_description());
  GtkWidget *widget = format_module->funcs.get_widget();
  gtk_widget_show(widget);
  format_module->funcs.generate_pipeline();
  current_format_module = format_module;
}


void
gnac_profiles_properties_update_textbuffer(const gchar *pipeline)
{
  GtkWidget *text_view =
      gnac_profiles_properties_get_widget("gstreamer_pipeline_text_view");
  GtkTextBuffer *pipeline_text_buffer =
      gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view));

  gtk_text_buffer_set_text(pipeline_text_buffer, pipeline,
      g_utf8_strlen(pipeline, -1));
}


static void
gnac_profiles_properties_display_status_message(const gchar *error_message)
{
  GtkWidget *widget = gnac_profiles_properties_get_widget(
      "hbox-properties-status");

  if (!error_message) {
    gtk_widget_hide(widget);
    return;
  }

  gtk_widget_show_all(widget);
  widget = gnac_profiles_properties_get_widget("label-properties-status-error");
  gtk_label_set_text(GTK_LABEL(widget), error_message);
}


static void
gnac_profiles_properties_reset(void)
{
  gnac_profiles_properties_display_status_message(NULL);
  gnac_profiles_properties_update_textbuffer("");
  gnac_profiles_properties_name_description_set_text("", "");

  GList *temp;
  GList *list = g_hash_table_get_values(formats);

  for (temp = list; temp; temp = g_list_next(temp)) {
    ((FormatModule *) temp->data)->funcs.set_fields(NULL);
  }

  g_list_free(list);
}


static void
gnac_profiles_properties_name_set_text(const gchar *name)
{
  if (!name) return;

  GtkWidget *widget = gnac_profiles_properties_get_widget("name_entry");
  gtk_entry_set_text(GTK_ENTRY(widget), name);
  current_profile_name = name;
}


static void
gnac_profiles_properties_description_set_text(const gchar *description)
{
  if (!description) return;

  GtkTextBuffer *text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(
      gnac_profiles_properties_get_widget("profile_description_text_view")));
  gtk_text_buffer_set_text(text_buffer, description,
      g_utf8_strlen(description, -1));
}


static void
gnac_profiles_properties_name_description_set_text(const gchar *name,
                                                   const gchar *description)
{
  gnac_profiles_properties_name_set_text(name);
  gnac_profiles_properties_description_set_text(description);
}


static void
gnac_profiles_properties_on_save(GtkWidget *widget,
                                 gpointer   data)
{
  StandardCallBack call_back = (StandardCallBack) data;
  GtkWidget *window = gnac_profiles_properties_get_widget("window1");
  
  GError *error = NULL;
  current_format_module->funcs.generate_pipeline();
  gpointer profile = current_format_module->funcs.generate_audio_profile(&error);
  if (!profile) {
    if (error) {
      gnac_profiles_properties_display_status_message(error->message);
      g_clear_error(&error);
    }
    return;
  }

  GtkWidget *text_view = gnac_profiles_properties_get_widget(
      "gstreamer_pipeline_text_view");
  AudioProfileGeneric *generic = AUDIO_PROFILE_GET_GENERIC(profile);

  if (!generic->pipeline) {
    generic->pipeline = gnac_profiles_utils_text_view_get_text(
        GTK_TEXT_VIEW(text_view));
  }

  widget = gnac_profiles_properties_get_widget("name_entry");
  const gchar *name = gtk_entry_get_text(GTK_ENTRY(widget));

  if (!gnac_profiles_properties_is_valid_profile_name(name)) return;

  gnac_profiles_properties_display_status_message(NULL);

  generic->name = g_strdup(name);
  widget = gnac_profiles_properties_get_widget("profile_description_text_view");
  generic->description = gnac_profiles_utils_text_view_get_text(
      GTK_TEXT_VIEW(widget));

  current_format_module->funcs.save_profile_in_file(profile);

  call_back(widget, profile);

  gtk_widget_hide(window);
}


static gboolean
gnac_profiles_properties_is_valid_profile_name(const gchar *name)
{
  if (gnac_utils_string_is_null_or_empty(name)) {
    gnac_profiles_properties_display_status_message(
        _("The profile name must be non-empty"));
    return FALSE;
  }

  if (!gnac_profiles_properties_is_valid_filename_chars(name)) {
    gchar *message = g_strconcat(
        _("The profile name cannot contain the following characters:"),
        " ", forbidden_chars_string, NULL);
    gnac_profiles_properties_display_status_message(message);
    g_free(message);
    return FALSE;
  }

  if (!gnac_utils_str_equal(current_profile_name, name)
      && gnac_profiles_properties_saved_profiles_contain_name(name))
  {
      gnac_profiles_properties_display_status_message(
          _("This name is already used by another profile."));
      return FALSE;
  }

  return TRUE;
}


gboolean
gnac_profiles_properties_on_name_entry_key_pressed(GtkWidget   *widget,
                                                   GdkEventKey *event,
                                                   gpointer     data)
{
  if (gnac_ui_utils_event_is_return_key(event)) {
    GtkWidget *widget = gnac_profiles_properties_get_widget("save-button");
    gtk_button_clicked(GTK_BUTTON(widget));
    return TRUE;
  }

  if (gnac_profiles_properties_is_valid_filename_char(event->keyval)) {
    gnac_profiles_properties_display_status_message(NULL);
    return FALSE;
  }

  gchar *message = g_strconcat(
      _("Profile name cannot contain the following characters:"),
      " ", forbidden_chars_string, NULL);
  gnac_profiles_properties_display_status_message(message);
  g_free(message);

  return TRUE;
}


gboolean
gnac_profiles_properties_on_window_key_pressed(GtkWidget   *widget,
                                               GdkEventKey *event,
                                               gpointer     data)
{
  if (gnac_ui_utils_event_is_escape_key(event)) {
    GtkWidget *widget = gnac_profiles_properties_get_widget("cancel-button");
    gtk_button_clicked(GTK_BUTTON(widget));
    return TRUE;
  }

  return FALSE;
}


static gboolean
gnac_profiles_properties_is_valid_filename_char(guint keyval)
{
  guint i;
  for (i = 0; i < G_N_ELEMENTS(forbidden_chars_keys); i++) {
    if (keyval == forbidden_chars_keys[i]) {
      return FALSE;
    }
  }

  return TRUE;
}


static gboolean
gnac_profiles_properties_is_valid_filename_chars(const gchar *chars)
{
  guint i, j;
  glong len = g_utf8_strlen(chars, -1);

  for (i = 0; i < len; i++) {
    for (j = 0; j < G_N_ELEMENTS(forbidden_chars); j++) {
      if (chars[i] == forbidden_chars[j]) {
        return FALSE;
      }
    }
  }

  return TRUE;
}


void
gnac_profiles_properties_on_cancel(GtkWidget *widget,
                                   gpointer   data)
{
  GtkWidget *window = gnac_profiles_properties_get_widget("window1");
  gtk_widget_hide(window);
}


gboolean
gnac_profiles_properties_on_delete_event(GtkWidget *widget,
                                         GdkEvent  *event,
                                         gpointer   user_data)
{
  gnac_profiles_properties_on_cancel(NULL, NULL);
  return TRUE;
}


gpointer
gnac_profiles_properties_load_profile_from_file(const gchar  *path,
                                               const gchar   *filename,
                                               GError       **error)
{
  g_return_val_if_fail(!error || !*error, NULL);

  AudioProfileGeneric *profile;
  XMLDoc *doc = gnac_profiles_default_load_generic_audio_profile(path, &profile);
  if (!doc || !(profile->format_id)) {
    *error = g_error_new(g_quark_from_static_string("gnac"), 0,
        _("File \"%s\" is not a valid profile file."), filename);
    gnac_profiles_xml_engine_free_doc_xpath(doc);
    return NULL;
  }

  FormatModule *format_module = g_hash_table_lookup(formats, profile->format_id);
  if (!format_module) {
    *error = g_error_new(g_quark_from_static_string("gnac"), 0,
        _("Format defined by id \"%s\" in file \"%s\" is not supported."),
        profile->format_id, filename);
    gnac_profiles_utils_free_audio_profile_generic(profile);
    gnac_profiles_xml_engine_free_doc_xpath(doc);
    return NULL;
  }

  profile = format_module->funcs.load_specific_properties(doc, profile);
  
  gnac_profiles_xml_engine_free_doc_xpath(doc);

  return profile;
}


gboolean
gnac_profiles_properties_saved_profiles_contain_name(const gchar *name)
{
  gchar *path;
  
  if (g_str_has_suffix(name, ".xml")) {
    path = g_strconcat(GNAC_SAVED_PROFILES_URL(name), NULL);
  } else {
    path = g_strconcat(GNAC_SAVED_PROFILES_URL_WITH_EXT(name), NULL);
  }

  GFile *file = g_file_new_for_path(path);
  gboolean exists = g_file_query_exists(file, NULL);

  g_free(path);
  g_object_unref(file);

  return exists; 
}


void
gnac_profiles_properties_save_profile(gpointer profile)
{
  const gchar *format_id = AUDIO_PROFILE_GET_GENERIC(profile)->format_id;
  if (!format_id) return;

  FormatModule *format_module = g_hash_table_lookup(formats, format_id);
  if (format_module) {
    format_module->funcs.save_profile_in_file(profile);
  }
}


gchar *
gnac_profiles_properties_filter_text_for_display(const gchar *text,
                                                 gint         chars_to_display)
{
  if (!text) return NULL;

  gchar *filtered;
  
  glong length = g_utf8_strlen(text, -1);

  if (length > chars_to_display) {
    gchar *prefix;
    prefix = g_strndup(text, length);
    filtered = g_strconcat(prefix, "...", NULL);
    g_free(prefix);
  } else {
    filtered = g_strdup(text);
  }

  g_strdelimit(filtered, "\n\t\r\b\f", ' ');

  return filtered;
}
