/*
 * Copyright (c) 2002, 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 <stdlib.h>
#include <glib/gi18n.h>
#include "streamtuner.h"

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

#define SHOUTCAST_ROOT		"http://www.shoutcast.com/"

#define MAX_STREAMS_PER_PAGE	100 /* enforced by SHOUTcast */

#define PARSE_ERROR		st_handler_notice(shoutcast_handler, _("parse error at %s"), G_STRLOC)

#define CONFIG_STREAM_LIMIT_ENABLED	"stream-limit-enabled"
#define CONFIG_STREAM_LIMIT		"stream-limit"

#define MIN_STREAM_LIMIT	0
#define MAX_STREAM_LIMIT	9999

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

typedef struct
{
  STStream	stream;

  char		*genre;
  char		*description;
  char		*now_playing;
  int		listeners;
  int		max;
  int		bitrate;
  char		*url_postfix;
  char		*homepage;

  /*
   * url_list may be set after the stream has entered streamtuner, so
   * we need to protect it.
   */
  GSList	*url_list;
  GMutex	*url_list_mutex;
} SHOUTcastStream;

enum {
  FIELD_GENRE,
  FIELD_DESCRIPTION,
  FIELD_NOW_PLAYING,
  FIELD_LISTENERS,
  FIELD_MAX,
  FIELD_BITRATE,
  FIELD_URL_POSTFIX,
  FIELD_HOMEPAGE,
  FIELD_URL_LIST
};

typedef struct
{
  GNode			**categories;
  GList			**streams;

  int			page;
  int			npages;

  GNode			*parent_node;
  SHOUTcastStream	*stream;
} ReloadInfo;

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

static STPlugin *shoutcast_plugin = NULL;
static STHandler *shoutcast_handler = NULL;

static GtkWidget *preferences_stream_limit_check;
static GtkWidget *preferences_stream_limit_spin;
static GtkWidget *preferences_stream_limit_label;

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

static gboolean	reload_cb	(STCategory	*category,
				 GNode		**categories,
				 GList		**streams,
				 gpointer	data,
				 GError		**err);

static void	reload_body_cb		(const char	*line,
					 gpointer	data);

static SHOUTcastStream *stream_new_cb (gpointer data);
static void stream_field_get_cb (SHOUTcastStream	*stream,
				 STHandlerField		*field,
				 GValue			*value,
				 gpointer		data);
static void stream_field_set_cb (SHOUTcastStream	*stream,
				 STHandlerField		*field,
				 const GValue		*value,
				 gpointer		data);
static void stream_stock_field_get_cb (SHOUTcastStream		*stream,
				       STHandlerStockField	stock_field,
				       GValue			*value,
				       gpointer			data);
static void stream_free_cb	(SHOUTcastStream	*stream,
				 gpointer		data);

static void stream_get_url_list	(SHOUTcastStream	*stream,
				 GValue			*value);

static gboolean stream_resolve		(SHOUTcastStream	*stream,
					 GError			**err);

static gboolean stream_resolve_cb	(SHOUTcastStream	*stream,
					 gpointer		data,
					 GError			**err);
static gboolean	stream_tune_in_cb	(SHOUTcastStream	*stream,
					 gpointer		data,
					 GError			**err);
static gboolean	stream_record_cb	(SHOUTcastStream	*stream,
					 gpointer		data,
					 GError			**err);
static gboolean	stream_browse_cb	(SHOUTcastStream	*stream,
					 gpointer		data,
					 GError			**err);

static int	search_url_cb		(STCategory		*category);

static GtkWidget *	preferences_widget_new_cb	(gpointer        data);
static void		preferences_update_sensitivity	(void);
static void		preferences_stream_limit_toggled_h        (GtkToggleButton *button,
								   gpointer        user_data);
static void		preferences_stream_limit_changed_h        (GtkSpinButton   *spin,
								   gpointer        user_data);

static void	init_handler		(void);
static gboolean check_api_version	(GError **err);

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

static SHOUTcastStream *
stream_new_cb (gpointer data)
{
  SHOUTcastStream *stream;

  stream = g_new0(SHOUTcastStream, 1);
  stream->url_list_mutex = g_mutex_new();

  return stream;
}

static void
stream_field_get_cb (SHOUTcastStream *stream,
		     STHandlerField *field,
		     GValue *value,
		     gpointer data)
{
  switch (field->id)
    {
    case FIELD_GENRE:
      g_value_set_string(value, stream->genre);
      break;

    case FIELD_DESCRIPTION:
      g_value_set_string(value, stream->description);
      break;

    case FIELD_NOW_PLAYING:
      g_value_set_string(value, stream->now_playing);
      break;

    case FIELD_LISTENERS:
      g_value_set_int(value, stream->listeners);
      break;

    case FIELD_MAX:
      g_value_set_int(value, stream->max);
      break;

    case FIELD_BITRATE:
      g_value_set_int(value, stream->bitrate);
      break;

    case FIELD_URL_POSTFIX:
      g_value_set_string(value, stream->url_postfix);
      break;

    case FIELD_HOMEPAGE:
      g_value_set_string(value, stream->homepage);
      break;

    case FIELD_URL_LIST:
      stream_get_url_list(stream, value);
      break;
      
    default:
      g_return_if_reached();
    }
}

static void
stream_field_set_cb (SHOUTcastStream *stream,
		     STHandlerField *field,
		     const GValue *value,
		     gpointer data)
{
  switch (field->id)
    {
    case FIELD_GENRE:
      stream->genre = g_value_dup_string(value);
      break;

    case FIELD_DESCRIPTION:
      stream->description = g_value_dup_string(value);
      break;

    case FIELD_NOW_PLAYING:
      stream->now_playing = g_value_dup_string(value);
      break;

    case FIELD_LISTENERS:
      stream->listeners = g_value_get_int(value);
      break;

    case FIELD_MAX:
      stream->max = g_value_get_int(value);
      break;

    case FIELD_BITRATE:
      stream->bitrate = g_value_get_int(value);
      break;

    case FIELD_URL_POSTFIX:
      stream->url_postfix = g_value_dup_string(value);
      break;

    case FIELD_HOMEPAGE:
      stream->homepage = g_value_dup_string(value);
      break;

    case FIELD_URL_LIST:
      {
	GValueArray *value_array;
	int i;

	value_array = g_value_get_boxed(value);
	for (i = 0; i < value_array->n_values; i++)
	  {
	    GValue *url_value = g_value_array_get_nth(value_array, i);
	    stream->url_list = g_slist_append(stream->url_list, g_value_dup_string(url_value));
	  }

	break;
      }
      
    default:
      g_return_if_reached();
    }
}

static void
stream_stock_field_get_cb (SHOUTcastStream *stream,
			   STHandlerStockField stock_field,
			   GValue *value,
			   gpointer data)
{
  switch (stock_field)
    {
    case ST_HANDLER_STOCK_FIELD_NAME:
      g_value_set_string(value, stream->description);
      break;

    case ST_HANDLER_STOCK_FIELD_GENRE:
      g_value_set_string(value, stream->genre);
      break;

    case ST_HANDLER_STOCK_FIELD_DESCRIPTION:
      /* nop */
      break;

    case ST_HANDLER_STOCK_FIELD_HOMEPAGE:
      g_value_set_string(value, stream->homepage);
      break;
      
    case ST_HANDLER_STOCK_FIELD_URI_LIST:
      stream_get_url_list(stream, value);
      break;
    }
}

static void
stream_free_cb (SHOUTcastStream *stream, gpointer data)
{
  GSList *l;

  g_free(stream->genre);
  g_free(stream->description);
  g_free(stream->now_playing);
  g_free(stream->url_postfix);
  g_free(stream->homepage);

  for (l = stream->url_list; l; l = l->next)
    g_free(l->data);

  g_slist_free(stream->url_list);
  g_mutex_free(stream->url_list_mutex);

  st_stream_free((STStream *) stream);
}

static void
stream_get_url_list (SHOUTcastStream *stream, GValue *value)
{
  GValueArray *value_array;
  GSList *l;
  
  g_return_if_fail(stream != NULL);
  g_return_if_fail(value != NULL);

  value_array = g_value_array_new(0);

  g_mutex_lock(stream->url_list_mutex);
  for (l = stream->url_list; l; l = l->next)
    {
      GValue url_value = { 0, };
      
      g_value_init(&url_value, G_TYPE_STRING);
      g_value_set_string(&url_value, l->data);
      g_value_array_append(value_array, &url_value);
      g_value_unset(&url_value);
    }
  g_mutex_unlock(stream->url_list_mutex);
  
  g_value_take_boxed(value, value_array);
}

static gboolean
stream_resolve (SHOUTcastStream *stream, GError **err)
{
  gboolean already_resolved;
  STTransferSession *session;
  char *url;
  char *playlist;
  gboolean status;

  g_return_val_if_fail(stream != NULL, FALSE);

  g_mutex_lock(stream->url_list_mutex);
  already_resolved = stream->url_list != NULL;
  g_mutex_unlock(stream->url_list_mutex);

  if (already_resolved)
    return TRUE;		/* already resolved */

  url = g_strconcat(SHOUTCAST_ROOT, stream->url_postfix, NULL);
  session = st_transfer_session_new();
  status = st_transfer_session_get(session, url, ST_TRANSFER_UTF8, NULL, &playlist, err);
  st_transfer_session_free(session);
  g_free(url);

  if (status)
    {
      gboolean empty;

      g_mutex_lock(stream->url_list_mutex);
      stream->url_list = st_pls_parse(playlist);
      empty = stream->url_list == NULL;
      g_mutex_unlock(stream->url_list_mutex);

      g_free(playlist);

      if (empty)
	{
	  g_set_error(err, 0, 0, _("stream is empty"));
	  return FALSE;
	}
    }
  
  return status;
}

static gboolean
stream_resolve_cb (SHOUTcastStream *stream, gpointer data, GError **err)
{
  return stream_resolve(stream, err);
}

static gboolean
stream_tune_in_cb (SHOUTcastStream *stream, gpointer data, GError **err)
{
  char *m3uname;
  gboolean status;
  
  if (! stream_resolve(stream, err))
    return FALSE;

  g_mutex_lock(stream->url_list_mutex);
  m3uname = st_m3u_mktemp("streamtuner.shoutcast.XXXXXX", stream->url_list, err);
  g_mutex_unlock(stream->url_list_mutex);

  if (! m3uname)
    return FALSE;
  
  status = st_action_run("play-m3u", m3uname, err);
  g_free(m3uname);

  return status;
}

static gboolean
stream_record_cb (SHOUTcastStream *stream, gpointer data, GError **err)
{
  gboolean status;

  if (! stream_resolve(stream, err))
    return FALSE;

  g_mutex_lock(stream->url_list_mutex);
  status = st_action_run("record-stream", stream->url_list->data, err);
  g_mutex_unlock(stream->url_list_mutex);

  return status;
}

static gboolean
stream_browse_cb (SHOUTcastStream *stream, gpointer data, GError **err)
{
  if (! stream->homepage) /* older versions of the plugin didn't have this field */
    {
      g_set_error(err, 0, 0, _("the stream is too old, please reload"));
      return FALSE;
    }

  return st_action_run("view-web", stream->homepage, err);
}

static gboolean
reload_cb (STCategory *category,
	   GNode **categories,
	   GList **streams,
	   gpointer data,
	   GError **err)
{
  ReloadInfo info;
  STTransferSession *session;
  gboolean status;
  int stream_limit;
  int requested_streams = 0;
  int received_streams = 0;

  g_return_val_if_fail(category != NULL, FALSE);
  g_return_val_if_fail(category->url_postfix != NULL, FALSE);

  *categories = g_node_new(NULL);
  *streams = NULL;

  info.categories = categories;
  info.streams = streams;

  session = st_transfer_session_new();

  stream_limit = st_handler_config_get_boolean(shoutcast_handler, CONFIG_STREAM_LIMIT_ENABLED)
    ? st_handler_config_get_int(shoutcast_handler, CONFIG_STREAM_LIMIT)
    : (! strcmp(category->name, "__main") ? 500 : -1); /* [1] */

  /*
   * [1] The main category contains all the SHOUTcast streams (a lot),
   * so we just load the first 500.
   */
  
  do
    {
      char *url;
      int rows;

      if (requested_streams != 0 && st_is_aborted())
	{
	  status = FALSE;
	  break;
	}

      rows = stream_limit == -1
	? MAX_STREAMS_PER_PAGE
	: MIN(stream_limit - received_streams, MAX_STREAMS_PER_PAGE);
      
      url = g_strdup_printf(SHOUTCAST_ROOT "directory/?numresult=%i&startat=%i%s", rows, requested_streams, category->url_postfix);

      requested_streams += rows;

      info.page = 0;
      info.npages = 0;
      info.parent_node = NULL;
      info.stream = NULL;

      status = st_transfer_session_get_by_line(session,
					       url,
					       ST_TRANSFER_UTF8 | ST_TRANSFER_PARSE_HTTP_CHARSET | ST_TRANSFER_PARSE_HTML_CHARSET,
					       NULL,
					       NULL,
					       reload_body_cb,
					       &info,
					       err);
      g_free(url);

      received_streams = g_list_length(*streams);

      if (info.stream)
	{
	  stream_free_cb(info.stream, NULL);
	  if (status) /* only display warning if the transfer was otherwise correct */
	    PARSE_ERROR;
	}
    }
  while (status && info.page > 0 && info.page < info.npages
	 && (stream_limit == -1 || received_streams < stream_limit));
      
  st_transfer_session_free(session);
  
  return status;
}

static void
reload_body_cb (const char *line, gpointer data)
{
  ReloadInfo *info = data;
  char *s1, *s2, *s3, *s4, *s5;
  char *word1, *word2;

  if ((s1 = strstr(line, "sbin/shoutcast-playlist.pls"))
      && (s2 = st_strstr_span(s1, "filename.pls")))
    {
      if (info->stream)	/* a malformed stream remains, free it */
	{
	  PARSE_ERROR;
	  stream_free_cb(info->stream, NULL);
	}
	  
      info->stream = stream_new_cb(NULL);
      info->stream->url_postfix = st_sgml_ref_expand_len(s1, s2 - s1);
    }
  else if (info->page < 2
	   && (((s1 = st_str_has_prefix_span(line, "\t<OPTION VALUE=\""))
		|| (s1 = st_str_has_prefix_span(line, "\t\t<OPTION VALUE=\"")))
	       && (s2 = strstr(s1, "\">"))))
    {
      STCategory *category;
      GNode *node;
      char *escaped;

      category = st_category_new();
      
      category->name = st_sgml_ref_expand_len(s1, s2 - s1);
      category->label = st_sgml_ref_expand(s2 + 2);
      
      escaped = st_transfer_escape(category->name);
      category->url_postfix = g_strconcat("&sgenre=", escaped, NULL);
      g_free(escaped);
      
      node = g_node_new(category);
      
      if (g_str_has_prefix(category->label, " - "))
	{
	  if (info->parent_node)
	    {
	      char *new_label;

	      new_label = g_strdup(category->label + 3);
	      g_free(category->label);
	      category->label = new_label;

	      g_node_append(info->parent_node, node);
	    }
	  else
	    {
	      PARSE_ERROR;
	      st_category_free(category);
	      g_node_destroy(node);
	    }
	}
      else
	{
	  g_node_append(*(info->categories), node);
	  info->parent_node = node;
	}
    }
  else if ((s1 = st_strstr_span(line, "<b>["))
	   && (s2 = strstr(s1, "]</font>"))
	   && (s3 = st_strstr_span(s2, "href=\""))
	   && (s4 = strstr(s3, "\">"))
	   && (s5 = strstr(s4, "</a>")))
    {
      if (info->stream)
	{
	  g_free(info->stream->genre);
	  info->stream->genre = st_sgml_ref_expand_len(s1, s2 - s1);
	  g_free(info->stream->homepage);
	  info->stream->homepage = st_sgml_ref_expand_len(s3, s4 - s3);
	  g_free(info->stream->description);
	  s4 += 2; info->stream->description = st_sgml_ref_expand_len(s4, s5 - s4);
	}
      else
	PARSE_ERROR;
    }
  else if ((s1 = st_strstr_span(line, "Now Playing:</font> "))
	   && (s2 = strstr(s1, "</font>")))
    {
      if (info->stream)
	{
	  g_free(info->stream->now_playing);
	  info->stream->now_playing = st_sgml_ref_expand_len(s1, s2 - s1);
	}
      else
	PARSE_ERROR;
    }
  else if ((s1 = st_str_has_prefix_span(line, "Page "))
	   && (s2 = strstr(s1, " of ")))
    {
      word1 = st_sgml_ref_expand_len(s1, s2 - s1);
      s2 += 4; word2 = st_sgml_ref_expand_len(s2, strspn(s2, ST_NUMERIC));

      if (st_str_like(word1, ST_NUMERIC) && st_str_like(word2, ST_NUMERIC))
	{
	  info->page = atoi(word1);
	  info->npages = atoi(word2);
	}
      else
	PARSE_ERROR;

      g_free(word1);
      g_free(word2);
    }
  else if ((s1 = st_strstr_span(line, "<font face="))
	   && (s2 = st_strchr_span(s1, '>'))
	   && (s3 = strstr(s2, "</font>")))
    {
      word1 = st_sgml_ref_expand_len(s2, s3 - s2);
      if (st_str_like(word1, ST_NUMERIC "/"))
	{
	  if ((s1 = strchr(word1, '/')))	/* listeners */
	    {
	      *s1 = 0;
	      if (info->stream)
		{
		  info->stream->listeners = atoi(word1);
		  info->stream->max = atoi(s1 + 1);
		}
	      else
		PARSE_ERROR;
	    }
	  else					/* bitrate */
	    {
	      if (info->stream)
		{
		  info->stream->bitrate = atoi(word1);
		  
		  if (info->stream->genre && info->stream->description && info->stream->homepage)
		    {
		      ((STStream *) info->stream)->name =
			g_strdup_printf("%s%s%i",
					info->stream->genre,
					info->stream->description,
					info->stream->bitrate);
		      
		      *(info->streams) = g_list_append(*(info->streams), info->stream);
		    }
		  else
		    {
		      PARSE_ERROR;
		      stream_free_cb(info->stream, NULL);
		    }
		  
		  info->stream = NULL;
		}
	      else
		PARSE_ERROR;
	    }
	}
      g_free(word1);
    }
}

static gboolean
search_url_cb (STCategory *category)
{
  char *str;

  str = st_search_dialog();
  if (str)
    {
      char *escaped;

      g_free(category->label);
      category->label = g_strdup_printf(_("Search results for \"%s\""), str);

      escaped = st_transfer_escape(str);
      g_free(str);

      g_free(category->url_postfix);
      category->url_postfix = g_strconcat("&s=", escaped, NULL);
      g_free(escaped);

      return TRUE;
    }
  else
    return FALSE;
}

static void
init_handler (void)
{
  GNode *stock_categories;
  STCategory *category;
  STHandlerField *field;

  shoutcast_handler = st_handler_new_from_plugin(shoutcast_plugin);

  st_handler_set_description(shoutcast_handler, _("SHOUTcast Yellow Pages"));
  st_handler_set_home(shoutcast_handler, "http://www.shoutcast.com/");
  
  stock_categories = g_node_new(NULL);

  category = st_category_new();
  category->name = "__main";
  category->label = _("Top streams");
  category->url_postfix = "&sgenre=TopTen";
  
  g_node_append_data(stock_categories, category);
  
  category = st_category_new();
  category->name = "__search";
  category->label = g_strdup(_("Search"));
  category->url_cb = search_url_cb;

  g_node_append_data(stock_categories, category);

  st_handler_set_stock_categories(shoutcast_handler, stock_categories);

  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_RELOAD, reload_cb, NULL);

  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_NEW, stream_new_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_FIELD_GET, stream_field_get_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_FIELD_SET, stream_field_set_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET, stream_stock_field_get_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_FREE, stream_free_cb, NULL);

  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_RESOLVE, stream_resolve_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_TUNE_IN, stream_tune_in_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_RECORD, stream_record_cb, NULL);
  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_STREAM_BROWSE, stream_browse_cb, NULL);

  st_handler_bind(shoutcast_handler, ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW, preferences_widget_new_cb, NULL);

  field = st_handler_field_new(FIELD_GENRE,
			       _("Genre"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The stream genre"));
  st_handler_add_field(shoutcast_handler, field);

  field = st_handler_field_new(FIELD_DESCRIPTION,
			       _("Description"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The stream description"));
  st_handler_add_field(shoutcast_handler, field);

  field = st_handler_field_new(FIELD_NOW_PLAYING,
			       _("Now playing"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The currently playing song"));
  st_handler_add_field(shoutcast_handler, field);
  
  field = st_handler_field_new(FIELD_LISTENERS,
			       _("Listeners"),
			       G_TYPE_INT,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The current number of listeners"));
  st_handler_add_field(shoutcast_handler, field); 

  field = st_handler_field_new(FIELD_MAX,
			       _("Max"),
			       G_TYPE_INT,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The maximum number of listeners"));
  st_handler_add_field(shoutcast_handler, field);

  field = st_handler_field_new(FIELD_BITRATE,
			       _("Bitrate"),
			       G_TYPE_INT,
			       ST_HANDLER_FIELD_VISIBLE);
  st_handler_field_set_description(field, _("The stream bitrate, in kilobits per seconds"));
  st_handler_add_field(shoutcast_handler, field);

  st_handler_add_field(shoutcast_handler, st_handler_field_new(FIELD_URL_POSTFIX,
							       _("URL postfix"),
							       G_TYPE_STRING,
							       0));

  field = st_handler_field_new(FIELD_HOMEPAGE,
			       _("Homepage"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_START_HIDDEN);
  st_handler_field_set_description(field, _("The stream homepage URL"));
  st_handler_add_field(shoutcast_handler, field);
  
  field = st_handler_field_new(FIELD_URL_LIST,
			       _("URL list"),
			       G_TYPE_VALUE_ARRAY,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_START_HIDDEN);
  st_handler_field_set_description(field, _("The stream listen URL list"));
  st_handler_add_field(shoutcast_handler, field);

  st_handler_config_register(shoutcast_handler,
			     g_param_spec_boolean(CONFIG_STREAM_LIMIT_ENABLED,
						  NULL,
						  NULL,
						  FALSE,
						  G_PARAM_READWRITE));
  st_handler_config_register(shoutcast_handler,
			     g_param_spec_int(CONFIG_STREAM_LIMIT,
					      NULL,
					      NULL,
					      MIN_STREAM_LIMIT,
					      MAX_STREAM_LIMIT,
					      100,
					      G_PARAM_READWRITE));

  st_handlers_add(shoutcast_handler);
}

static GtkWidget *
preferences_widget_new_cb (gpointer data)
{
  GtkWidget *box1;
  GtkWidget *box2;
  GtkWidget *section;

  box1 = gtk_hbox_new(FALSE, 12);
  preferences_stream_limit_check = gtk_check_button_new_with_mnemonic(_("_Load at most:"));
  box2 = gtk_hbox_new(FALSE, 6);
  preferences_stream_limit_spin = gtk_spin_button_new_with_range(MIN_STREAM_LIMIT, MAX_STREAM_LIMIT, 1);
  preferences_stream_limit_label = gtk_label_new(_("streams per category"));
  gtk_box_pack_start(GTK_BOX(box2), preferences_stream_limit_spin, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(box2), preferences_stream_limit_label, FALSE, FALSE, 0);

  gtk_box_pack_start(GTK_BOX(box1), preferences_stream_limit_check, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(box1), box2, FALSE, FALSE, 0);

  gtk_widget_show_all(box1);
  section = st_hig_section_new(_("Streams Limit"), box1);

  st_set_tooltip(preferences_stream_limit_check, _("If this option is enabled, the number of streams to download will be limited."));
  st_set_tooltip(preferences_stream_limit_spin, _("The maximum number of streams to download per category"));

  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(preferences_stream_limit_check), st_handler_config_get_boolean(shoutcast_handler, CONFIG_STREAM_LIMIT_ENABLED));
  gtk_spin_button_set_value(GTK_SPIN_BUTTON(preferences_stream_limit_spin), st_handler_config_get_int(shoutcast_handler, CONFIG_STREAM_LIMIT));

  preferences_update_sensitivity();

  g_signal_connect(preferences_stream_limit_check, "toggled", G_CALLBACK(preferences_stream_limit_toggled_h), NULL);
  g_signal_connect(preferences_stream_limit_spin, "value-changed", G_CALLBACK(preferences_stream_limit_changed_h), NULL);
  
  return section;
}

static void
preferences_update_sensitivity (void)
{
  gboolean stream_limit_enabled;

  stream_limit_enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(preferences_stream_limit_check));
  gtk_widget_set_sensitive(preferences_stream_limit_spin, stream_limit_enabled);
  gtk_widget_set_sensitive(preferences_stream_limit_label, stream_limit_enabled);
}

static void
preferences_stream_limit_toggled_h (GtkToggleButton *button, gpointer user_data)
{
  st_handler_config_set_boolean(shoutcast_handler, CONFIG_STREAM_LIMIT_ENABLED, gtk_toggle_button_get_active(button));
  preferences_update_sensitivity();
}

static void
preferences_stream_limit_changed_h (GtkSpinButton *spin, gpointer user_data)
{
  st_handler_config_set_int(shoutcast_handler, CONFIG_STREAM_LIMIT, gtk_spin_button_get_value_as_int(spin));
}

static gboolean
check_api_version (GError **err)
{
  if (st_check_api_version(5, 8))
    return TRUE;
  else
    {
      g_set_error(err, 0, 0, _("API version mismatch"));
      return FALSE;
    }
}

G_MODULE_EXPORT gboolean
plugin_get_info (STPlugin *plugin, GError **err)
{
  GdkPixbuf *pixbuf;

  if (! check_api_version(err))
    return FALSE;

  shoutcast_plugin = plugin;

  st_plugin_set_name(plugin, "shoutcast");
  st_plugin_set_label(plugin, "SHOUTcast");

  pixbuf = st_pixbuf_new_from_file(UIDIR "/shoutcast.png");
  if (pixbuf)
    {
      st_plugin_set_icon_from_pixbuf(plugin, pixbuf);
      g_object_unref(pixbuf);
    }

  return TRUE;
}

G_MODULE_EXPORT gboolean
plugin_init (GError **err)
{
  if (! check_api_version(err))
    return FALSE;

  init_handler();
  
  st_action_register("play-m3u", _("Listen to a .m3u file"), "xmms %q");
  st_action_register("record-stream", _("Record a stream"), "xterm -e streamripper %q");
  st_action_register("view-web", _("Open a web page"), "epiphany %q");

  return TRUE;
}
