/*
 * 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 "sgtk-util.h"
#include "sg-util.h"
#include "st-browser-tab.h"
#include "st-browser-tab-label.h"
#include "st-category-view.h"
#include "st-dialog-api.h"
#include "st-statusbar.h"
#include "st-stream-view.h"
#include "st-handler.h"
#include "st-thread.h"
#include "st-settings.h"
#include "st-category-store.h"
#include "st-stream-store.h"
#include "st-util.h"
#include "st-cache.h"
#include "st-shell.h"
#include "st-bookmarks.h"

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

enum {
  STREAM_TASK_ADDED,
  STREAM_TASK_REMOVED,
  LAST_SIGNAL
};

enum {
  PROP_0,
  PROP_HANDLER,
  PROP_RUNNING
};
    
typedef struct
{
  STBrowserTab		*tab;
  STCategoryBag		*category_bag;
  STThread		*thread;
} CategoryTask;

struct _STBrowserTabPrivate
{
  GSList		*running_categories;
  GSList		*stream_tasks;
  GNode			*pending_categories;
};

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

static GObjectClass *parent_class = NULL;
static unsigned int browser_tab_signals[LAST_SIGNAL] = { 0 };

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

static void st_browser_tab_class_init	(STBrowserTabClass	*class);
static void st_browser_tab_init		(STBrowserTab		*tab);

static GObject *st_browser_tab_constructor (GType type,
					    unsigned int n_construct_properties,
					    GObjectConstructParam *construct_params);

static void st_browser_tab_set_property	(GObject	*object,
					 unsigned int	prop_id,
					 const GValue	*value,
					 GParamSpec	*pspec);
static void st_browser_tab_get_property (GObject *object,
					 unsigned int prop_id,
					 GValue *value,
					 GParamSpec *pspec);

static void st_browser_tab_update_counters	(STBrowserTab    *tab);

static void st_browser_tab_reload_thread (gpointer data);

static void st_browser_tab_run_category_task (STBrowserTab *tab,
					      STCategoryBag *category_bag,
					      STThreadCallback callback);
static void st_browser_tab_category_task_print_cb (const char *str,
						   gpointer data);
static void st_browser_tab_category_task_set_progress_cb (double progress,
							  gpointer data);
static void st_browser_tab_category_task_cleanup_cb (gpointer data);
static void st_browser_tab_category_task_data_destroy_cb (gpointer data);

static void st_browser_tab_set_category_running (STBrowserTab *tab,
						 STCategoryBag *bag,
						 gboolean running);
static void st_browser_tab_update_running (STBrowserTab *tab);

static void st_browser_tab_category_selection_changed_h (STCategoryView *view,
							 gpointer user_data);

static void st_browser_tab_cache_load_streams_thread (gpointer data);
static gboolean st_browser_tab_cache_load_streams_progress_cb (int n,
							       int total,
							       gpointer data);

static void st_browser_tab_set_streams (STBrowserTab *tab,
					STCategoryBag *category_bag,
					STStreamStore *streams);

static void st_browser_tab_paned_notify_position_h	(GObject     *object,
							 GParamSpec  *pspec,
							 gpointer    user_data);

static void st_browser_tab_stream_task_thread (gpointer data);
static void st_browser_tab_stream_task_add_bookmarks_thread (gpointer data);
static void st_browser_tab_stream_task_cleanup_cb (gpointer data);
static void st_browser_tab_stream_task_data_destroy_cb (gpointer data);

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

GType
st_browser_tab_get_type (void)
{
  static GType browser_tab_type = 0;
  
  if (! browser_tab_type)
    {
      static const GTypeInfo browser_tab_info = {
	sizeof(STBrowserTabClass),
	NULL,
	NULL,
	(GClassInitFunc) st_browser_tab_class_init,
	NULL,
	NULL,
	sizeof(STBrowserTab),
	0,
	(GInstanceInitFunc) st_browser_tab_init,
      };
      
      browser_tab_type = g_type_register_static(GTK_TYPE_HPANED,
						"STBrowserTab",
						&browser_tab_info,
						0);
    }

  return browser_tab_type;
}

static void
st_browser_tab_class_init (STBrowserTabClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STBrowserTabPrivate));

  object_class->constructor = st_browser_tab_constructor;
  object_class->set_property = st_browser_tab_set_property;
  object_class->get_property = st_browser_tab_get_property;

  g_object_class_install_property(object_class,
				  PROP_HANDLER,
				  g_param_spec_pointer("handler",
						       NULL,
						       NULL,
						       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
  g_object_class_install_property(object_class,
				  PROP_RUNNING,
				  g_param_spec_boolean("running",
						       NULL,
						       NULL,
						       FALSE,
						       G_PARAM_READABLE));

  browser_tab_signals[STREAM_TASK_ADDED] = g_signal_new("stream-task-added",
							ST_TYPE_BROWSER_TAB,
							G_SIGNAL_RUN_FIRST,
							G_STRUCT_OFFSET(STBrowserTabClass, stream_task_added),
							NULL,
							NULL,
							g_cclosure_marshal_VOID__POINTER,
							G_TYPE_NONE,
							1,
							G_TYPE_POINTER);
  browser_tab_signals[STREAM_TASK_REMOVED] = g_signal_new("stream-task-removed",
							  ST_TYPE_BROWSER_TAB,
							  G_SIGNAL_RUN_FIRST,
							  G_STRUCT_OFFSET(STBrowserTabClass, stream_task_removed),
							  NULL,
							  NULL,
							  g_cclosure_marshal_VOID__POINTER,
							  G_TYPE_NONE,
							  1,
							  G_TYPE_POINTER);
}

static void
st_browser_tab_init (STBrowserTab *tab)
{
  tab->priv = G_TYPE_INSTANCE_GET_PRIVATE(tab, ST_TYPE_BROWSER_TAB, STBrowserTabPrivate);

  tab->handler = NULL;
  tab->running = FALSE;
  tab->category_view = NULL;
  tab->stream_view = NULL;
}

static GObject *
st_browser_tab_constructor (GType type,
			    unsigned int n_construct_properties,
			    GObjectConstructParam *construct_params)
{
  GObject *object;
  STBrowserTab *tab;
  GtkWidget *scrolled;

  object = parent_class->constructor(type, n_construct_properties, construct_params);
  tab = ST_BROWSER_TAB(object);

  if (ST_HANDLER_HAS_CATEGORIES(tab->handler))
    {
      STCategoryStore *categories;

      scrolled = gtk_scrolled_window_new(FALSE, FALSE);
      gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
				     GTK_POLICY_AUTOMATIC,
				     GTK_POLICY_AUTOMATIC);

      categories = st_handler_get_categories(tab->handler);
      tab->category_view = (STCategoryView *) st_category_view_new(categories);
      g_object_unref(categories);

      gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(tab->category_view));
      gtk_paned_add1(GTK_PANED(tab), scrolled);
      gtk_widget_show_all(scrolled);

      g_signal_connect(tab->category_view,
		       "selection-changed",
		       G_CALLBACK(st_browser_tab_category_selection_changed_h),
		       tab);
    }

  scrolled = gtk_scrolled_window_new(FALSE, FALSE);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
				 GTK_POLICY_AUTOMATIC,
				 GTK_POLICY_AUTOMATIC);

  tab->stream_view = (STStreamView *) st_stream_view_new(tab->handler);

  gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(tab->stream_view));
  gtk_paned_add2(GTK_PANED(tab), scrolled);
  gtk_widget_show_all(scrolled);

  tab->label = st_browser_tab_label_new(tab);
  gtk_widget_show(tab->label);

  gtk_paned_set_position(GTK_PANED(tab), st_handler_get_paned_position(tab->handler));

  g_signal_connect(tab,
		   "notify::position",
		   G_CALLBACK(st_browser_tab_paned_notify_position_h),
		   NULL);

  return object;
}

static void
st_browser_tab_set_property (GObject *object,
			     unsigned int prop_id,
			     const GValue *value,
			     GParamSpec *pspec)
{
  STBrowserTab *tab = ST_BROWSER_TAB(object);

  switch (prop_id)
    {
    case PROP_HANDLER:
      tab->handler = g_value_get_pointer(value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
    }
}

static void
st_browser_tab_get_property (GObject *object,
			     unsigned int prop_id,
			     GValue *value,
			     GParamSpec *pspec)
{
  STBrowserTab *tab = ST_BROWSER_TAB(object);

  switch (prop_id)
    {
    case PROP_RUNNING:
      g_value_set_boolean(value, tab->running);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
    }
}

GtkWidget *
st_browser_tab_new (STHandler *handler)
{
  return g_object_new(ST_TYPE_BROWSER_TAB,
		      "handler", handler,
		      NULL);
}

void
st_browser_tab_update_sensitivity (STBrowserTab *tab)
{
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  st_stream_view_update_sensitivity(tab->stream_view);
}

gboolean
st_browser_tab_can_reload (STBrowserTab *tab)
{
  g_return_val_if_fail(ST_IS_BROWSER_TAB(tab), FALSE);

  return ST_HANDLER_EVENT_HAS_RELOAD(tab->handler);
}

void
st_browser_tab_reload (STBrowserTab *tab)
{
  STCategoryBag *category_bag;
  STThread *task_thread;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  category_bag = st_handler_get_selected_category(tab->handler);
  g_return_if_fail(category_bag != NULL);

  task_thread = st_category_bag_get_task_thread(category_bag);
  if (task_thread)
    st_thread_abort(task_thread);

  st_browser_tab_run_category_task(tab, category_bag, st_browser_tab_reload_thread);
  g_object_unref(category_bag);
}

static void
st_browser_tab_reload_thread (gpointer data)
{
  CategoryTask *task = data;
  gboolean status;
  GNode *categories;
  STStreamStore *stream_store;
  GError *err = NULL;

  GDK_THREADS_ENTER();
  st_thread_printf(task->thread, TRUE, _("Reloading..."));
  gdk_flush();
  GDK_THREADS_LEAVE();
  
  if (st_category_bag_apply_url_cb(task->category_bag))
    {
      status = st_handler_reload(task->tab->handler,
				 task->category_bag,
				 &categories,
				 &stream_store,
				 &err);
      st_thread_validate_callback_return(task->thread, status, err);
    }
  else
    status = FALSE;		/* aborted in url_cb */

  GDK_THREADS_ENTER();

  if (status)
    {
      /* defer setting categories */

      if (task->tab->priv->pending_categories)
	sg_objects_free_node(task->tab->priv->pending_categories);

      task->tab->priv->pending_categories = categories;

      /* set streams */

      st_browser_tab_set_streams(task->tab, task->category_bag, stream_store);
      if (stream_store)
	g_object_unref(stream_store);
    }

  st_thread_cleanup(task->thread);

  gdk_flush();
  GDK_THREADS_LEAVE();

  if (! status && err)
    {
      char *normalized;

      normalized = st_dialog_normalize(err->message);
      st_error_dialog(_("Unable to reload"), "%s", normalized);
      
      g_free(normalized);
      g_error_free(err);
    }
}

static void
st_browser_tab_run_category_task (STBrowserTab *tab,
				  STCategoryBag *category_bag,
				  STThreadCallback callback)
{
  CategoryTask *task;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(ST_IS_CATEGORY_BAG(category_bag));
  g_return_if_fail(callback != NULL);

  task = g_new(CategoryTask, 1);

  task->tab = g_object_ref(tab);
  task->category_bag = g_object_ref(category_bag);
  task->thread = st_thread_new(tab->handler);
  task->thread->thread = callback;
  task->thread->print = st_browser_tab_category_task_print_cb;
  task->thread->set_progress = st_browser_tab_category_task_set_progress_cb;
  task->thread->cleanup = st_browser_tab_category_task_cleanup_cb;
  task->thread->data = task;
  task->thread->data_destroy = st_browser_tab_category_task_data_destroy_cb;
  st_category_bag_set_task_thread(category_bag, task->thread);

  st_browser_tab_set_category_running(tab, category_bag, TRUE);
}

static void
st_browser_tab_category_task_print_cb (const char *str, gpointer data)
{
  CategoryTask *task = data;
  
  st_statusbar_print(ST_STATUSBAR(task->category_bag->statusbar), task->category_bag->statusbar_context_task, str);
}

static void
st_browser_tab_category_task_set_progress_cb (double progress, gpointer data)
{
  CategoryTask *task = data;

  if (progress < 0) /* -1 means "pulse" */
    gtk_progress_bar_pulse(ST_STATUSBAR(task->category_bag->statusbar)->progress_bar);
  else
    gtk_progress_bar_set_fraction(ST_STATUSBAR(task->category_bag->statusbar)->progress_bar, progress);
}

static void
st_browser_tab_category_task_cleanup_cb (gpointer data)
{
  CategoryTask *task = data;

  st_browser_tab_set_category_running(task->tab, task->category_bag, FALSE);
}

static void
st_browser_tab_category_task_data_destroy_cb (gpointer data)
{
  CategoryTask *task = data;

  g_object_unref(task->tab);
  g_object_unref(task->category_bag);
  g_free(task);
}

static void
st_browser_tab_set_category_running (STBrowserTab *tab,
				     STCategoryBag *bag,
				     gboolean running)
{
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(ST_IS_CATEGORY_BAG(bag));

  tab->priv->running_categories = running
    ? g_slist_append(tab->priv->running_categories, bag)
    : g_slist_remove(tab->priv->running_categories, bag);

  st_statusbar_set_active(ST_STATUSBAR(bag->statusbar), running);
  st_browser_tab_update_running(tab);

  if (running)
    st_thread_run(st_category_bag_get_task_thread(bag));
  else
    {
      st_category_bag_set_task_thread(bag, NULL);
      gtk_statusbar_pop(GTK_STATUSBAR(bag->statusbar), bag->statusbar_context_task);
    }

  if (! tab->priv->running_categories && tab->priv->pending_categories)
    {
      STCategoryStore *category_store;
      
      /* the last reload has just finished, process the pending categories */
      
      category_store = st_handler_get_categories(tab->handler);
      st_category_store_merge_node(category_store, tab->priv->pending_categories);
      g_object_unref(category_store);

      sg_objects_free_node(tab->priv->pending_categories);
      tab->priv->pending_categories = NULL;
    }
}

static void
st_browser_tab_update_running (STBrowserTab *tab)
{
  gboolean new_running;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  new_running = tab->priv->running_categories != NULL || tab->priv->stream_tasks != NULL;
  if (new_running != tab->running)
    {
      tab->running = new_running;
      g_object_notify(G_OBJECT(tab), "running");
    }
}

gboolean
st_browser_tab_can_stop (STBrowserTab *tab)
{
  g_return_val_if_fail(ST_IS_BROWSER_TAB(tab), FALSE);

  return tab->running;
}

void
st_browser_tab_stop (STBrowserTab *tab)
{
  GSList *list;
  GSList *l;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(st_browser_tab_can_stop(tab));

  /*
   * We use copies, because the lists will be modified by
   * st_thread_abort().
   */

  list = g_slist_copy(tab->priv->running_categories);
  SG_LIST_FOREACH(l, list)
    {
      STCategoryBag *bag = l->data;
      st_thread_abort(st_category_bag_get_task_thread(bag));
    }
  g_slist_free(list);

  list = g_slist_copy(tab->priv->stream_tasks);
  SG_LIST_FOREACH(l, list)
    {
      STBrowserTabStreamTask *task = l->data;
      st_thread_abort(task->thread);
    }
  g_slist_free(list);
}

gboolean
st_browser_tab_can_visit_homepage (STBrowserTab *tab)
{
  g_return_val_if_fail(ST_IS_BROWSER_TAB(tab), FALSE);

  return st_handler_get_home(tab->handler) != NULL;
}

void
st_browser_tab_visit_homepage (STBrowserTab *tab)
{
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(st_browser_tab_can_visit_homepage(tab));

  st_visit_homepage(st_handler_get_home(tab->handler));
}

gboolean
st_browser_tab_can_present_preferences (STBrowserTab *tab)
{
  g_return_val_if_fail(ST_IS_BROWSER_TAB(tab), FALSE);

  return st_handler_event_is_bound(tab->handler, ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW);
}

void
st_browser_tab_present_preferences (STBrowserTab *tab)
{
  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(st_browser_tab_can_present_preferences(tab));

  st_shell_present_handler_preferences(st_shell, tab->handler);
}

void
st_browser_tab_update (STBrowserTab *tab)
{
  STCategoryBag *category_bag;
  STStreamStore *streams;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  category_bag = st_handler_get_selected_category(tab->handler);
  g_return_if_fail(category_bag != NULL);

  streams = st_handler_get_streams(tab->handler, ST_CATEGORY(category_bag)->name);
  st_browser_tab_set_streams(tab, category_bag, streams);

  if (! st_category_bag_get_task_thread(category_bag))
    {
      if (ST_HANDLER_EVENT_HAS_RELOAD(tab->handler) && st_settings.always_reload)
	st_browser_tab_reload(tab);
      else if (! streams)
	{
	  if (st_cache_has_streams(tab->handler, ST_CATEGORY(category_bag)->name))
	    st_browser_tab_run_category_task(tab, category_bag, st_browser_tab_cache_load_streams_thread);
	  else if (ST_HANDLER_EVENT_HAS_RELOAD(tab->handler))
	    st_browser_tab_reload(tab);
	}
    }

  g_object_unref(category_bag);
  if (streams)
    g_object_unref(streams);
}

static void
st_browser_tab_cache_load_streams_thread (gpointer data)
{
  CategoryTask *task = data;
  STStreamStore *stream_store;
  GError *err = NULL;

  GDK_THREADS_ENTER();
  st_statusbar_print(ST_STATUSBAR(task->category_bag->statusbar),
		     task->category_bag->statusbar_context_task,
		     _("Loading streams from cache..."));
  gdk_flush();
  GDK_THREADS_LEAVE();

  stream_store = st_cache_load_streams(task->tab->handler,
				       ST_CATEGORY(task->category_bag)->name,
				       st_browser_tab_cache_load_streams_progress_cb,
				       task,
				       &err);

  GDK_THREADS_ENTER();

  if (stream_store)
    {
      st_handler_set_streams(task->tab->handler, ST_CATEGORY(task->category_bag)->name, stream_store);
      st_browser_tab_set_streams(task->tab, task->category_bag, stream_store);

      g_object_unref(stream_store);
    }

  st_thread_cleanup(task->thread);

  gdk_flush();
  GDK_THREADS_LEAVE();

  if (! stream_store && err)
    {
      char *normalized;

      normalized = st_dialog_normalize(err->message);
      st_error_dialog(_("Unable to load streams from cache"), "%s", normalized);
      
      g_free(normalized);
      g_error_free(err);
    }
}

static gboolean
st_browser_tab_cache_load_streams_progress_cb (int n, int total, gpointer data)
{
  CategoryTask *task = data;

  if (st_thread_is_aborted(task->thread))
    return TRUE;		/* abort */

  GDK_THREADS_ENTER();
  st_thread_printf(task->thread, FALSE,
		   ngettext("Loading streams from cache (%i stream out of %i)...",
			    "Loading streams from cache (%i streams out of %i)...",
			    n),
		   n, total);
  st_thread_set_progress(task->thread, FALSE, (double) n / total);
  gdk_flush();
  GDK_THREADS_LEAVE();

  return FALSE;			/* continue */
}

static void
st_browser_tab_update_counters (STBrowserTab *tab)
{
  GtkTreeModel *model;
  GString *string;
  int n_streams;
  STCategoryBag *category_bag;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));

  string = g_string_new(NULL);
  
  if (tab->category_view)
    {
      int n_categories;
      
      model = gtk_tree_view_get_model(GTK_TREE_VIEW(tab->category_view));
      n_categories = model ? sgtk_tree_model_get_count(model) : 0;
      
      g_string_append_printf(string, ngettext("%i category",
					      "%i categories",
					      n_categories),
			     n_categories);
    }
  
  if (*string->str)
    g_string_append(string, ", ");
  
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(tab->stream_view));
  n_streams = model ? sgtk_tree_model_get_count(model) : 0;
  g_string_append_printf(string, ngettext("%i stream",
					  "%i streams",
					  n_streams),
			 n_streams);
  
  category_bag = st_handler_get_selected_category(tab->handler);
  g_return_if_fail(category_bag != NULL);
  
  st_statusbar_print(ST_STATUSBAR(category_bag->statusbar), category_bag->statusbar_context_count, string->str);
  g_string_free(string, TRUE);
}

static void
st_browser_tab_category_selection_changed_h (STCategoryView *view,
					     gpointer user_data)
{
  STBrowserTab *tab = user_data;

  st_browser_tab_update(tab);
}

static void
st_browser_tab_set_streams (STBrowserTab *tab,
			    STCategoryBag *category_bag,
			    STStreamStore *streams)
{
  GtkTreeModel *model;
  STCategoryBag *selected_category;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(ST_IS_CATEGORY_BAG(category_bag));

  selected_category = st_handler_get_selected_category(tab->handler);
  g_return_if_fail(selected_category != NULL);

  if (category_bag == selected_category)
    {
      model = gtk_tree_view_get_model(GTK_TREE_VIEW(tab->stream_view));
      if (model)
	g_signal_handlers_disconnect_by_func(model, st_browser_tab_update_counters, tab);

      st_stream_view_set_store(tab->stream_view, streams);
      if (streams)
	g_object_connect(streams,
			 "swapped-signal::row-deleted", st_browser_tab_update_counters, tab,
			 "swapped-signal::row-inserted", st_browser_tab_update_counters, tab,
			 NULL);

      st_browser_tab_update_counters(tab);
    }
}

static void
st_browser_tab_paned_notify_position_h (GObject *object,
					GParamSpec *pspec,
					gpointer user_data)
{
  st_handler_set_paned_position(ST_BROWSER_TAB(object)->handler,
				gtk_paned_get_position(GTK_PANED(object)));
}

gboolean
st_browser_tab_can_run_stream_task (STBrowserTab *tab,
				    STBrowserTabStreamTaskType type)
{
  g_return_val_if_fail(ST_IS_BROWSER_TAB(tab), FALSE);

  switch (type)
    {
    case ST_BROWSER_TAB_STREAM_TASK_TUNE_IN:
      return ST_HANDLER_EVENT_HAS_TUNE_IN(tab->handler)
	&& st_stream_view_has_selected_streams(tab->stream_view);
      
    case ST_BROWSER_TAB_STREAM_TASK_RECORD:
      return st_handler_event_is_bound(tab->handler, ST_HANDLER_EVENT_STREAM_RECORD)
	&& st_stream_view_has_selected_streams(tab->stream_view);

    case ST_BROWSER_TAB_STREAM_TASK_BROWSE:
      return st_handler_event_is_bound(tab->handler, ST_HANDLER_EVENT_STREAM_BROWSE)
	&& st_stream_view_has_selected_streams(tab->stream_view);

    case ST_BROWSER_TAB_STREAM_TASK_ADD_BOOKMARKS:
      return tab->handler != st_bookmarks_handler
	&& st_stream_view_has_selected_streams(tab->stream_view);

    default:
      g_return_val_if_reached(FALSE);
    }
}

void
st_browser_tab_run_stream_task (STBrowserTab *tab,
				STBrowserTabStreamTaskType type)
{
  STBrowserTabStreamTask *task;

  g_return_if_fail(ST_IS_BROWSER_TAB(tab));
  g_return_if_fail(st_browser_tab_can_run_stream_task(tab, type));

  task = g_new0(STBrowserTabStreamTask, 1);
  task->tab = tab;
  task->type = type;
  task->streams = st_stream_view_get_selected_streams(tab->stream_view);
  task->thread = st_thread_new(tab->handler);
  task->thread->thread = type == ST_BROWSER_TAB_STREAM_TASK_ADD_BOOKMARKS
    ? st_browser_tab_stream_task_add_bookmarks_thread
    : st_browser_tab_stream_task_thread;
  task->thread->cleanup = st_browser_tab_stream_task_cleanup_cb;
  task->thread->data = task;
  task->thread->data_destroy = st_browser_tab_stream_task_data_destroy_cb;
  
  tab->priv->stream_tasks = g_slist_append(tab->priv->stream_tasks, task);
  st_browser_tab_update_running(tab);

  g_signal_emit(tab, browser_tab_signals[STREAM_TASK_ADDED], 0, task);
  st_thread_run(task->thread);
}

static void
st_browser_tab_stream_task_thread (gpointer data)
{
  STBrowserTabStreamTask *task = data;
  gboolean status;
  GError *err = NULL;

  /* if there's no multiple callback, only keep the first stream */

  if (task->type != ST_BROWSER_TAB_STREAM_TASK_TUNE_IN
      || ! st_handler_event_is_bound(task->tab->handler, ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE))
    {
      sg_objects_free(task->streams->next);
      task->streams->next = NULL;
    }

  /* set running */

  GDK_THREADS_ENTER();
  g_slist_foreach(task->streams, (GFunc) st_stream_bag_running_ref, NULL);
  task->running_reffed_streams = g_slist_copy(task->streams);
  gdk_flush();
  GDK_THREADS_LEAVE();

  /* run the appropriate callback */

  if (task->type == ST_BROWSER_TAB_STREAM_TASK_TUNE_IN)
    {
      if (st_handler_event_is_bound(task->tab->handler, ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE))
	status = st_stream_bag_tune_in_multiple(task->streams, &err);
      else
	status = st_stream_bag_tune_in(task->streams->data, &err);
    }
  else if (task->type == ST_BROWSER_TAB_STREAM_TASK_RECORD)
    status = st_stream_bag_record(task->streams->data, &err);
  else if (task->type == ST_BROWSER_TAB_STREAM_TASK_BROWSE)
    status = st_stream_bag_browse(task->streams->data, &err);
  else
    g_return_if_reached();

  /* finish */

  GDK_THREADS_ENTER();
  st_thread_cleanup(task->thread);
  gdk_flush();
  GDK_THREADS_LEAVE();

  st_thread_validate_callback_return(task->thread, status, err);

  if (err)
    {
      const char *primary[] = {
	N_("Unable to tune in"),
	N_("Unable to record"),
	N_("Unable to browse")
      };
      char *normalized;
  
      normalized = st_dialog_normalize(err->message);
      st_error_dialog(_(primary[task->type]), "%s", normalized);
      
      g_free(normalized);
      g_error_free(err);
    }
}

static void
st_browser_tab_stream_task_add_bookmarks_thread (gpointer data)
{
  STBrowserTabStreamTask *task = data;
  GSList *l;

  SG_LIST_FOREACH(l, task->streams)
    {
      STStreamBag *bag = l->data;
      
      GDK_THREADS_ENTER();

      st_stream_bag_running_ref(bag);
      task->running_reffed_streams = g_slist_append(task->running_reffed_streams, bag);

      gdk_flush();
      GDK_THREADS_LEAVE();

      if (st_handler_event_is_bound(task->tab->handler, ST_HANDLER_EVENT_STREAM_RESOLVE))
	{
	  gboolean status;
	  GError *err = NULL;

	  status = st_stream_bag_resolve(bag, &err);
	  st_thread_validate_callback_return(task->thread, status, err);
	  
	  if (err)
	    {
	      char *normalized;

	      normalized = st_dialog_normalize(err->message);
	      st_error_dialog(_("Unable to resolve stream"), "%s", normalized);

	      g_free(normalized);
	      g_error_free(err);
	    }
	}
      
      GDK_THREADS_ENTER();

      st_stream_bag_running_unref(bag);
      st_bookmarks_add(bag);

      task->running_reffed_streams = g_slist_remove(task->running_reffed_streams, bag);

      gdk_flush();
      GDK_THREADS_LEAVE();
    }
}

static void
st_browser_tab_stream_task_cleanup_cb (gpointer data)
{
  STBrowserTabStreamTask *task = data;

  g_slist_foreach(task->running_reffed_streams, (GFunc) st_stream_bag_running_unref, NULL);

  task->tab->priv->stream_tasks = g_slist_remove(task->tab->priv->stream_tasks, task);
  st_browser_tab_update_running(task->tab);

  g_signal_emit(task->tab, browser_tab_signals[STREAM_TASK_REMOVED], 0, task);
}

static void
st_browser_tab_stream_task_data_destroy_cb (gpointer data)
{
  STBrowserTabStreamTask *task = data;

  sg_objects_free(task->streams);
  g_slist_free(task->running_reffed_streams);
  g_free(task);
}
