/*
 * Copyright (c) 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "sg-util.h"
#include "sgtk-hig.h"
#include "sgtk-message-dialog.h"
#include "sgtk-util.h"
#include "st-preferences-dialog.h"
#include "st-applications-preferences-page.h"
#include "st-general-preferences-page.h"
#include "st-network-preferences-page.h"
#include "st-plugins-preferences-page.h"
#include "st-settings.h"
#include "st-util.h"
#include "st-shell.h"
#include "st-handlers.h"
#include "st-handler.h"

/*** cpp *********************************************************************/

#define HANDLER_PAGE_NAME(handler_name) \
  g_strdup_printf("handler:%s", (handler_name))

/*** type definitions ********************************************************/

enum {
  COLUMN_PAGE,

  COLUMN_ICON,
  COLUMN_STOCK_ID,
  COLUMN_LABEL,

  N_COLUMNS
};

struct _STPreferencesDialogPrivate
{
  GtkWidget		*hbox;
  GtkWidget		*view;
  STPreferencesPage	*current_page;
};
  
typedef struct
{
  STPreferencesDialog	*dialog;
  const char		*name;
  gboolean		found;
} SelectPageInfo;

/*** variable declarations ***************************************************/

static gboolean first_time = TRUE;
static GSList *disabled_plugins = NULL;

/*** function declarations ***************************************************/

static void st_preferences_dialog_class_init	(STPreferencesDialogClass *class);
static void st_preferences_dialog_init		(STPreferencesDialog  *dialog);

static STPreferencesPage *st_preferences_dialog_handler_page_new (STHandler *handler);
static void st_preferences_dialog_append_page	(STPreferencesDialog  *dialog,
						 GtkTreeStore         *store,
						 GtkTreeIter          *iter,
						 GtkTreeIter          *parent,
						 STPreferencesPage    *page);

static void st_preferences_dialog_page_changed_h (GtkTreeSelection    *selection,
						  gpointer            user_data);
static void st_preferences_dialog_row_expanded_collapsed_h (GtkTreeView *view,
							    GtkTreeIter *iter,
							    GtkTreePath *path,
							    gpointer user_data);
static void st_preferences_dialog_response_h	(GtkDialog	      *dialog,
						 int		      response,
						 gpointer	      data);

static STPreferencesPage *st_preferences_dialog_get_selected_page (STPreferencesDialog *dialog);
static void st_preferences_dialog_select_page (STPreferencesDialog *dialog,
					       const char *name);
static gboolean st_preferences_dialog_select_page_cb (GtkTreeModel *model,
						      GtkTreePath *path,
						      GtkTreeIter *iter,
						      gpointer data);
static void st_preferences_dialog_select_iter (STPreferencesDialog *dialog,
					       GtkTreeIter *iter);

/*** implementation **********************************************************/

GType
st_preferences_dialog_get_type (void)
{
  static GType preferences_dialog_type = 0;
  
  if (! preferences_dialog_type)
    {
      static const GTypeInfo preferences_dialog_info = {
	sizeof(STPreferencesDialogClass),
	NULL,
	NULL,
	(GClassInitFunc) st_preferences_dialog_class_init,
	NULL,
	NULL,
	sizeof(STPreferencesDialog),
	0,
	(GInstanceInitFunc) st_preferences_dialog_init,
      };
      
      preferences_dialog_type = g_type_register_static(SGTK_TYPE_DIALOG,
						       "STPreferencesDialog",
						       &preferences_dialog_info,
						       0);
    }

  return preferences_dialog_type;
}

static void
st_preferences_dialog_class_init (STPreferencesDialogClass *class)
{
  g_type_class_add_private(class, sizeof(STPreferencesDialogPrivate));
}

static void
st_preferences_dialog_init (STPreferencesDialog *dialog)
{
  GSList *l;
  GtkWidget *scrolled;
  GtkTreeStore *store;
  GtkTreeIter iter;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GtkTreeSelection *selection;

  if (first_time)
    {
      SG_LIST_FOREACH(l, st_settings.disabled_plugins)
	disabled_plugins = g_slist_append(disabled_plugins, g_strdup(l->data));

      first_time = FALSE;
    }
  
  dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE(dialog, ST_TYPE_PREFERENCES_DIALOG, STPreferencesDialogPrivate);

  gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_HELP, GTK_RESPONSE_HELP);
  gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);

  gtk_window_set_title(GTK_WINDOW(dialog), _("streamtuner Preferences"));

  dialog->priv->hbox = gtk_hbox_new(FALSE, SGTK_HIG_DIALOG_CONTENTS_SPACING);

  store = gtk_tree_store_new(N_COLUMNS,
			     ST_TYPE_PREFERENCES_PAGE,
			     GDK_TYPE_PIXBUF,
			     G_TYPE_STRING,
			     G_TYPE_STRING);

  st_preferences_dialog_append_page(dialog,
				    store,
				    &iter,
				    NULL,
				    st_applications_preferences_page_new());
  st_preferences_dialog_append_page(dialog,
				    store,
				    &iter,
				    NULL,
				    st_general_preferences_page_new());
  st_preferences_dialog_append_page(dialog,
				    store,
				    &iter,
				    NULL,
				    st_network_preferences_page_new());
  st_preferences_dialog_append_page(dialog,
				    store,
				    &iter,
				    NULL,
				    st_plugins_preferences_page_new());

  SG_LIST_FOREACH(l, st_handlers_list)
    {
      STHandler *handler = l->data;

      if (st_handler_event_is_bound(handler, ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW))
	{
	  STPreferencesPage *page;

	  page = st_preferences_dialog_handler_page_new(handler);
	  if (page)
	    {
	      GtkTreeIter child_iter;
	      st_preferences_dialog_append_page(dialog,
						store,
						&child_iter,
						&iter,
						page);
	    }
	}
    }
  
  gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
				       COLUMN_LABEL,
				       GTK_SORT_ASCENDING);

  dialog->priv->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));

  if (st_settings.preferences_plugins_expanded)
    {
      GtkTreePath *path;

      path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
      gtk_tree_view_expand_row(GTK_TREE_VIEW(dialog->priv->view), path, FALSE);
      gtk_tree_path_free(path);
    }

  g_object_unref(store);

  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(dialog->priv->view), FALSE);

  column = gtk_tree_view_column_new();

  renderer = gtk_cell_renderer_pixbuf_new();
  g_object_set(renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
  gtk_tree_view_column_pack_start(column, renderer, FALSE);
  gtk_tree_view_column_set_attributes(column, renderer,
				      "pixbuf", COLUMN_ICON,
				      "stock-id", COLUMN_STOCK_ID,
				      NULL);

  renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(column, renderer, TRUE);
  gtk_tree_view_column_set_attributes(column, renderer, "text", COLUMN_LABEL, NULL);

  gtk_tree_view_append_column(GTK_TREE_VIEW(dialog->priv->view), column);

  scrolled = gtk_scrolled_window_new(NULL, NULL);
  /*
   * We don't want scrollbars, we just want the shadow (we don't use a
   * GtkFrame because it leaves one pixel between itself and its
   * child).
   */
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
				 GTK_POLICY_NEVER,
				 GTK_POLICY_NEVER);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);

  gtk_container_add(GTK_CONTAINER(scrolled), dialog->priv->view);

  gtk_box_pack_start(GTK_BOX(dialog->priv->hbox), scrolled, FALSE, FALSE, 0);
  gtk_container_add(GTK_CONTAINER(SGTK_DIALOG(dialog)->contents), dialog->priv->hbox);

  gtk_widget_show_all(scrolled);
  gtk_widget_show(dialog->priv->hbox);

  sgtk_window_link_size(GTK_WINDOW(dialog),
			&st_settings.preferences_window_width,
			&st_settings.preferences_window_height);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->priv->view));
  g_signal_connect(selection, "changed", G_CALLBACK(st_preferences_dialog_page_changed_h), dialog);

  g_object_connect(dialog->priv->view,
		   "signal::row-expanded", st_preferences_dialog_row_expanded_collapsed_h, GPOINTER_TO_INT(TRUE),
		   "signal::row-collapsed", st_preferences_dialog_row_expanded_collapsed_h, GPOINTER_TO_INT(FALSE),
		   NULL);

  g_signal_connect(dialog, "response", G_CALLBACK(st_preferences_dialog_response_h), NULL);

  st_preferences_dialog_select_page(dialog, st_settings.preferences_selected_page);
}

static STPreferencesPage *
st_preferences_dialog_handler_page_new (STHandler *handler)
{
  GtkWidget *widget;
  STPreferencesPage *page;
  const char *handler_name;
  char *page_name;
  
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(st_handler_event_is_bound(handler, ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW), NULL);
  
  widget = st_handler_event_preferences_widget_new(handler);
  if (! widget)
    return NULL;

  page = st_preferences_page_new();

  gtk_widget_show(widget);
  gtk_box_pack_start(GTK_BOX(page), widget, FALSE, FALSE, 0);

  handler_name = st_handler_get_name(handler);

  page_name = HANDLER_PAGE_NAME(handler_name);
  st_preferences_page_set_name(page, page_name);
  g_free(page_name);

  st_preferences_page_set_icon(page, st_handler_get_pixbuf(handler));
  st_preferences_page_set_label(page, st_handler_get_label(handler));
  
  if (! strcmp(handler_name, "live365"))
    st_preferences_page_set_help_link_id(page, "preferences-live365");
  else if (! strcmp(handler_name, "shoutcast"))
    st_preferences_page_set_help_link_id(page, "preferences-shoutcast");
  else if (! strcmp(handler_name, "punkcast.com.py"))
    st_preferences_page_set_help_link_id(page, "preferences-punkcast");

  return page;
}

/* takes @page ownership */
static void
st_preferences_dialog_append_page (STPreferencesDialog *dialog,
				   GtkTreeStore *store,
				   GtkTreeIter *iter,
				   GtkTreeIter *parent,
				   STPreferencesPage *page)
{
  GdkPixbuf *icon;
  GdkPixbuf *scaled;

  g_return_if_fail(ST_IS_PREFERENCES_DIALOG(dialog));
  g_return_if_fail(GTK_IS_TREE_STORE(store));
  g_return_if_fail(iter != NULL);
  g_return_if_fail(ST_IS_PREFERENCES_PAGE(page));

  /* take the page ownership */
  g_object_ref(page);
  gtk_object_sink(GTK_OBJECT(page));

  icon = st_preferences_page_get_icon(page);
  scaled = icon ? sgtk_pixbuf_scale(icon, GTK_ICON_SIZE_MENU) : NULL;

  gtk_tree_store_append(store, iter, parent);
  gtk_tree_store_set(store, iter,
		     COLUMN_PAGE, page,
		     COLUMN_ICON, scaled,
		     COLUMN_STOCK_ID, st_preferences_page_get_stock_id(page),
		     COLUMN_LABEL, st_preferences_page_get_label(page),
		     -1);

  if (scaled)
    g_object_unref(scaled);

  /* the store now holds a reference to the page */
  g_object_unref(page);
}

static void
st_preferences_dialog_page_changed_h (GtkTreeSelection *selection,
				      gpointer user_data)
{
  STPreferencesDialog *dialog = user_data;

  if (dialog->priv->current_page)
    {
      /*
       * The list store holds a reference to the page, so it will not
       * be destroyed here.
       */
      gtk_container_remove(GTK_CONTAINER(dialog->priv->hbox),
			   GTK_WIDGET(dialog->priv->current_page));
      dialog->priv->current_page = NULL;
    }

  g_free(st_settings.preferences_selected_page);
  st_settings.preferences_selected_page = NULL;

  dialog->priv->current_page = st_preferences_dialog_get_selected_page(dialog);
  if (dialog->priv->current_page)
    {
      gtk_box_pack_start(GTK_BOX(dialog->priv->hbox),
			 GTK_WIDGET(dialog->priv->current_page),
			 TRUE,
			 TRUE,
			 0);
      gtk_widget_show(GTK_WIDGET(dialog->priv->current_page));
      g_object_unref(dialog->priv->current_page); /* hbox owns it */

      st_settings.preferences_selected_page = g_strdup(st_preferences_page_get_name(dialog->priv->current_page));
    }
}

static void
st_preferences_dialog_row_expanded_collapsed_h (GtkTreeView *view,
						GtkTreeIter *iter,
						GtkTreePath *path,
						gpointer user_data)
{
  STPreferencesPage *page;

  gtk_tree_model_get(gtk_tree_view_get_model(view), iter, COLUMN_PAGE, &page, -1);
  if (! strcmp(st_preferences_page_get_name(page), "plugins"))
    st_settings.preferences_plugins_expanded = GPOINTER_TO_INT(user_data);
  g_object_unref(page);
}

static void
st_preferences_dialog_response_h (GtkDialog *dialog,
				  int response,
				  gpointer data)
{
  STPreferencesDialog *preferences_dialog = ST_PREFERENCES_DIALOG(dialog);

  switch (response)
    {
    case GTK_RESPONSE_HELP:
      {
	STPreferencesPage *page;

	page = st_preferences_dialog_get_selected_page(preferences_dialog);
	if (page)
	  {
	    st_show_help(st_preferences_page_get_help_link_id(page));
	    g_object_unref(page);
	  }
	else
	  st_show_help("preferences");

	break;
      }

    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_CLOSE:
      {
	GSList *l;
	gboolean plugins_changed = FALSE;

	gtk_widget_destroy(GTK_WIDGET(dialog));

	SG_LIST_FOREACH(l, disabled_plugins)
	  if (! sg_str_slist_contains(st_settings.disabled_plugins, l->data))
	    {
	      plugins_changed = TRUE;
	      break;
	    }

	if (! plugins_changed)
	  SG_LIST_FOREACH(l, st_settings.disabled_plugins)
	    if (! sg_str_slist_contains(disabled_plugins, l->data))
	      {
		plugins_changed = TRUE;
		break;
	      }

	if (plugins_changed)
	  {
	    GtkWidget *dialog;
	    int response;
	    
	    dialog = sgtk_message_dialog_new(st_shell ? st_shell_get_window(st_shell) : NULL,
					     GTK_STOCK_DIALOG_WARNING,
					     _("Restart streamtuner?"),
					     _("The plugins settings have changed, and streamtuner must be restarted for these changes to take effect. Would you like to restart streamtuner?"));

	    gtk_dialog_add_buttons(GTK_DIALOG(dialog),
				   GTK_STOCK_NO, GTK_RESPONSE_NO,
				   GTK_STOCK_YES, GTK_RESPONSE_YES,
				   NULL);
	    gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);

	    response = gtk_dialog_run(GTK_DIALOG(dialog));
	    gtk_widget_destroy(dialog);
	    
	    if (response == GTK_RESPONSE_YES)
	      {
		st_shell_set_restart(st_shell, TRUE);
		g_object_unref(st_shell);
	      }
	  }
      }
      break;
    }
}

static STPreferencesPage *
st_preferences_dialog_get_selected_page (STPreferencesDialog *dialog)
{
  GtkTreeSelection *selection;
  GtkTreeModel *model;
  GtkTreeIter iter;
  STPreferencesPage *page = NULL;

  g_return_val_if_fail(ST_IS_PREFERENCES_DIALOG(dialog), NULL);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->priv->view));
  if (gtk_tree_selection_get_selected(selection, &model, &iter))
    gtk_tree_model_get(model, &iter, COLUMN_PAGE, &page, -1);

  return page;
}

static void
st_preferences_dialog_select_page (STPreferencesDialog *dialog,
				   const char *name)
{
  GtkTreeModel *model;
  GtkTreeIter iter;

  g_return_if_fail(ST_IS_PREFERENCES_DIALOG(dialog));

  model = gtk_tree_view_get_model(GTK_TREE_VIEW(dialog->priv->view));

  if (name)
    {
      SelectPageInfo info;

      info.dialog = dialog;
      info.name = name;
      info.found = FALSE;

      gtk_tree_model_foreach(model, st_preferences_dialog_select_page_cb, &info);

      if (info.found)
	return;
    }

  if (gtk_tree_model_get_iter_first(model, &iter))
    st_preferences_dialog_select_iter(dialog, &iter);
}

static gboolean
st_preferences_dialog_select_page_cb (GtkTreeModel *model,
				      GtkTreePath *path,
				      GtkTreeIter *iter,
				      gpointer data)
{
  SelectPageInfo *info = data;
  STPreferencesPage *page;

  gtk_tree_model_get(model, iter, COLUMN_PAGE, &page, -1);
  if (! strcmp(st_preferences_page_get_name(page), info->name))
    {
      st_preferences_dialog_select_iter(info->dialog, iter);
      info->found = TRUE;
    }
  g_object_unref(page);

  return info->found;
}

static void
st_preferences_dialog_select_iter (STPreferencesDialog *dialog,
				   GtkTreeIter *iter)
{
  GtkTreeSelection *selection;

  g_return_if_fail(ST_IS_PREFERENCES_DIALOG(dialog));
  g_return_if_fail(iter != NULL);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(dialog->priv->view));
  gtk_tree_selection_select_iter(selection, iter);
}

GtkWidget *
st_preferences_dialog_new (GtkWindow *parent)
{
  STPreferencesDialog *dialog;

  dialog = g_object_new(ST_TYPE_PREFERENCES_DIALOG, NULL);

  if (parent)
    gtk_window_set_transient_for(GTK_WINDOW(dialog), parent);

  return GTK_WIDGET(dialog);
}

void
st_preferences_dialog_select_handler (STPreferencesDialog *dialog,
				      STHandler *handler)
{
  char *page_name;

  g_return_if_fail(ST_IS_PREFERENCES_DIALOG(dialog));
  g_return_if_fail(ST_IS_HANDLER(handler));

  page_name = HANDLER_PAGE_NAME(st_handler_get_name(handler));
  st_preferences_dialog_select_page(dialog, page_name);
  g_free(page_name);
}
