/*
 * Copyright (c) 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-util.h"
#include "st-handler.h"
#include "st-handlers.h"
#include "st-handler-field.h"
#include "st-stream-store.h"
#include "st-util.h"
#include "st-util-api.h"

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

#define ST_BOOKMARKS_STREAM(bag) \
  ((STBookmarksStream *) ST_STREAM((bag)))

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

typedef struct
{
  STStream	stream;

  char		*handler_name;
  STHandler	*handler;
  STStreamBag	*stream_bag;

  GdkPixbuf	*handler_icon;
  char		*name;
  char		*genre;
  char		*description;
} STBookmarksStream;

enum {
  FIELD_HANDLER_NAME,
  FIELD_STREAM_BAG,
  FIELD_HANDLER_ICON,
  FIELD_HANDLER_LABEL,
  FIELD_NAME,
  FIELD_GENRE,
  FIELD_DESCRIPTION,
  FIELD_HOMEPAGE,
  FIELD_URI_LIST
};

typedef struct
{
  STStreamBag	*bag;
  gboolean	taken;
} MakeUniqueInfo;

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

STHandler *st_bookmarks_handler = NULL;

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

static STBookmarksStream *st_bookmarks_stream_new_cb (gpointer data);
static void st_bookmarks_stream_set_handler (STBookmarksStream *stream,
					     STHandler *handler);
static void st_bookmarks_stream_field_get_cb (STBookmarksStream *stream,
					      STHandlerField *field,
					      GValue *value,
					      gpointer data);
static void st_bookmarks_stream_field_set_cb (STBookmarksStream *stream,
					      STHandlerField *field,
					      const GValue *value,
					      gpointer data);
static void st_bookmarks_stream_stock_field_get_cb (STBookmarksStream *stream,
						    STHandlerStockField stock_field,
						    GValue *value,
						    gpointer data);
static gboolean st_bookmarks_stream_modify_cb (STBookmarksStream *stream,
					       GSList *fields,
					       GSList *values,
					       gpointer data,
					       GError **err);
static gboolean st_bookmarks_stream_delete_cb (STBookmarksStream *stream,
					       gpointer data,
					       GError **err);
static void st_bookmarks_stream_free_cb (STBookmarksStream *stream,
					 gpointer data);

static GValueArray *st_bookmarks_value_array_new (STStreamBag *stream_bag);
static STStreamBag *st_bookmarks_stream_new (STHandler *handler,
					     GValueArray *value_array);

static gboolean st_bookmarks_stream_tune_in_cb (STBookmarksStream *stream,
						gpointer data,
						GError **err);
static gboolean st_bookmarks_stream_record_cb (STBookmarksStream *stream,
					       gpointer data,
					       GError **err);
static gboolean st_bookmarks_stream_browse_cb (STBookmarksStream *stream,
					       gpointer data,
					       GError **err);

static void st_bookmarks_make_unique_name (STStreamBag *bag,
					   STStreamStore *bookmarks);
static gboolean st_bookmarks_make_unique_name_cb (STStreamStore *store,
						  STStreamBag *bag,
						  gpointer data);

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

static STBookmarksStream *
st_bookmarks_stream_new_cb (gpointer data)
{
  return g_new0(STBookmarksStream, 1);
}

static void
st_bookmarks_stream_set_handler (STBookmarksStream *stream, STHandler *handler)
{
  g_return_if_fail(stream != NULL);

  stream->handler = handler;
  if (stream->handler)
    {
      GdkPixbuf *pixbuf;

      pixbuf = st_handler_get_pixbuf(stream->handler);
      if (pixbuf)
	stream->handler_icon = sgtk_pixbuf_scale(pixbuf, GTK_ICON_SIZE_MENU);
    }
}

static void
st_bookmarks_stream_field_get_cb (STBookmarksStream *stream,
				  STHandlerField *field,
				  GValue *value,
				  gpointer data)
{
  switch (field->id)
    {
    case FIELD_HANDLER_NAME:
      g_value_set_string(value, stream->handler ? st_handler_get_name(stream->handler) : NULL);
      break;
      
    case FIELD_STREAM_BAG:
      {
	GValueArray *value_array;

	value_array = st_bookmarks_value_array_new(stream->stream_bag);
	g_value_take_boxed(value, value_array);

	break;
      }

    case FIELD_HANDLER_ICON:
      g_value_set_object(value, stream->handler_icon);
      break;
      
    case FIELD_HANDLER_LABEL:
      g_value_set_string(value, stream->handler ? st_handler_get_label(stream->handler) : stream->handler_name);
      break;

    case FIELD_NAME:
      g_value_set_string(value, stream->name);
      break;

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

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

    case FIELD_HOMEPAGE:
      if (stream->stream_bag)
	st_stream_bag_get_stock_field_initialized(stream->stream_bag, ST_HANDLER_STOCK_FIELD_HOMEPAGE, value);
      break;

    case FIELD_URI_LIST:
      if (stream->stream_bag)
	st_stream_bag_get_stock_field_initialized(stream->stream_bag, ST_HANDLER_STOCK_FIELD_URI_LIST, value);
      break;

    default:
      g_return_if_reached();
    }
}

static void
st_bookmarks_stream_field_set_cb (STBookmarksStream *stream,
				  STHandlerField *field,
				  const GValue *value,
				  gpointer data)
{
  switch (field->id)
    {
    case FIELD_HANDLER_NAME:
      stream->handler_name = g_value_dup_string(value);
      st_bookmarks_stream_set_handler(stream, st_handlers_find_by_name(stream->handler_name));
      break;

    case FIELD_STREAM_BAG:
      if (stream->handler)
	{
	  GValueArray *value_array;

	  value_array = g_value_get_boxed(value);
	  stream->stream_bag = st_bookmarks_stream_new(stream->handler, value_array);
	}
      break;

    case FIELD_NAME:
      g_free(stream->name);		/* field is editable */
      stream->name = g_value_dup_string(value);
      break;

    case FIELD_GENRE:
      g_free(stream->genre);		/* field is editable */
      stream->genre = g_value_dup_string(value);
      break;

    case FIELD_DESCRIPTION:
      g_free(stream->description);	/* field is editable */
      stream->description = g_value_dup_string(value);
      break;

    default:
      g_return_if_reached();
    }
}

static void
st_bookmarks_stream_stock_field_get_cb (STBookmarksStream *stream,
					STHandlerStockField stock_field,
					GValue *value,
					gpointer data)
{
  switch (stock_field)
    {
    case ST_HANDLER_STOCK_FIELD_NAME:
      g_value_set_string(value, stream->name);
      break;

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

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

    case ST_HANDLER_STOCK_FIELD_HOMEPAGE:
      if (stream->stream_bag)
	st_stream_bag_get_stock_field_initialized(stream->stream_bag, ST_HANDLER_STOCK_FIELD_HOMEPAGE, value);
      break;

    case ST_HANDLER_STOCK_FIELD_URI_LIST:
      if (stream->stream_bag)
	st_stream_bag_get_stock_field_initialized(stream->stream_bag, ST_HANDLER_STOCK_FIELD_URI_LIST, value);
      break;
    }
}

static gboolean
st_bookmarks_stream_modify_cb (STBookmarksStream *stream,
			       GSList *fields,
			       GSList *values,
			       gpointer data,
			       GError **err)
{
  GSList *field = fields;
  GSList *value = values;

  while (field && value)
    {
      st_bookmarks_stream_field_set_cb(stream, field->data, value->data, NULL);

      field = field->next;
      value = value->next;
    }

  return TRUE;
}

static gboolean
st_bookmarks_stream_delete_cb (STBookmarksStream *stream,
			       gpointer data,
			       GError **err)
{
  return TRUE;			/* nop */
}

/*
 * Create a GValueArray from the values of the underlying stream
 * STREAM_BAG.
 */
static GValueArray *
st_bookmarks_value_array_new (STStreamBag *stream_bag)
{
  GValueArray *value_array;
  
  value_array = g_value_array_new(0);
  if (stream_bag)
    {
      GSList *l;

      g_return_val_if_fail(ST_IS_STREAM_BAG(stream_bag), NULL);

      SG_LIST_FOREACH(l, st_handler_get_fields(stream_bag->handler))
        {
	  STHandlerField *field = l->data;

	  if (! ST_HANDLER_FIELD_IS_VOLATILE(field))
	    {
	      GValue value = { 0, };

	      st_stream_bag_get_field(stream_bag, field, &value);
	      g_value_array_append(value_array, &value);
	      g_value_unset(&value);
	    }
	}
    }

  return value_array;
}

/*
 * Create an underlying stream belonging to HANDLER from the values of
 * VALUE_ARRAY.
 */
static STStreamBag *
st_bookmarks_stream_new (STHandler *handler, GValueArray *value_array)
{
  STStreamBag *stream;
  GSList *l;
  int i;
	      
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(value_array != NULL, NULL);

  stream = st_stream_bag_new(handler);
  ST_STREAM(stream)->name = g_strdup("");	/* dummy */

  l = st_handler_get_fields(handler);
  i = 0;

  while (l && i < value_array->n_values)
    {
      STHandlerField *field = l->data;

      if (! ST_HANDLER_FIELD_IS_VOLATILE(field))
	{
	  GValue *value;

	  value = g_value_array_get_nth(value_array, i);
	  if (G_VALUE_TYPE(value) == st_handler_field_get_type(field))
	    st_stream_bag_set_field(stream, field, value);

	  i += 1;
	}

      l = l->next;
    }

  return stream;
}

static void
st_bookmarks_stream_free_cb (STBookmarksStream *stream, gpointer data)
{
  if (stream->stream_bag)
    g_object_unref(stream->stream_bag);

  if (stream->handler_icon)
    g_object_unref(stream->handler_icon);

  g_free(stream->handler_name);
  g_free(stream->name);
  g_free(stream->genre);
  g_free(stream->description);

  st_stream_free((STStream *) stream);
}

static gboolean
st_bookmarks_stream_tune_in_cb (STBookmarksStream *stream,
				gpointer data,
				GError **err)
{
  if (stream->stream_bag)
    {
      if (st_handler_event_is_bound(stream->stream_bag->handler, ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE))
	{
	  GSList *streams = NULL;
	  gboolean status;

	  streams = g_slist_append(streams, stream->stream_bag);
	  status = st_stream_bag_tune_in_multiple(streams, err);
	  g_slist_free(streams);

	  return status;
	}
      else if (st_handler_event_is_bound(stream->stream_bag->handler, ST_HANDLER_EVENT_STREAM_TUNE_IN))
	return st_stream_bag_tune_in(stream->stream_bag, err);
      else
	{
	  g_set_error(err, 0, 0, _("the %s handler has no tune in capability"),
		      st_handler_get_label(stream->stream_bag->handler));
	  return FALSE;
	}
    }
  else
    {
      g_set_error(err, 0, 0, _("the %s handler is not available"), stream->handler_name);
      return FALSE;
    }
}

static gboolean
st_bookmarks_stream_record_cb (STBookmarksStream *stream,
			       gpointer data,
			       GError **err)
{
  if (stream->stream_bag)
    {
      if (st_handler_event_is_bound(stream->stream_bag->handler, ST_HANDLER_EVENT_STREAM_RECORD))
	return st_stream_bag_record(stream->stream_bag, err);
      else
	{
	  g_set_error(err, 0, 0, _("the %s handler has no record capability"),
		      st_handler_get_label(stream->stream_bag->handler));
	  return FALSE;
	}
    }
  else
    {
      g_set_error(err, 0, 0, _("the %s handler is not available"), stream->handler_name);
      return FALSE;
    }
}

static gboolean
st_bookmarks_stream_browse_cb (STBookmarksStream *stream,
			       gpointer data,
			       GError **err)
{
  if (stream->stream_bag)
    {
      if (st_handler_event_is_bound(stream->stream_bag->handler, ST_HANDLER_EVENT_STREAM_BROWSE))
	return st_stream_bag_browse(stream->stream_bag, err);
      else
	{
	  g_set_error(err, 0, 0, _("the %s handler has no browse capability"),
		      st_handler_get_label(stream->stream_bag->handler));
	  return FALSE;
	}
    }
  else
    {
      g_set_error(err, 0, 0, _("the %s handler is not available"), stream->handler_name);
      return FALSE;
    }
}

void
st_bookmarks_init (void)
{
  GdkPixbuf *pixbuf;
  STStreamStore *bookmarks;
  STHandlerField *field;

  st_bookmarks_handler = st_handler_new("bookmarks");

  st_handler_set_label(st_bookmarks_handler, _("Bookmarks"));
  st_handler_set_copyright(st_bookmarks_handler, "Copyright \302\251 2004 Jean-Yves Lefort");
  st_handler_set_description(st_bookmarks_handler, _("Favourite Streams"));
  st_handler_set_flags(st_bookmarks_handler, ST_HANDLER_NO_CATEGORIES);

  pixbuf = st_pixbuf_new_from_file(ST_PNG("bookmarks"));
  if (pixbuf)
    {
      st_handler_set_icon_from_pixbuf(st_bookmarks_handler, pixbuf);
      g_object_unref(pixbuf);
    }

  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_NEW, st_bookmarks_stream_new_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_FIELD_GET, st_bookmarks_stream_field_get_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_FIELD_SET, st_bookmarks_stream_field_set_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET, st_bookmarks_stream_stock_field_get_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_MODIFY, st_bookmarks_stream_modify_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_DELETE, st_bookmarks_stream_delete_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_FREE, st_bookmarks_stream_free_cb, NULL);

  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_TUNE_IN, st_bookmarks_stream_tune_in_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_RECORD, st_bookmarks_stream_record_cb, NULL);
  st_handler_bind(st_bookmarks_handler, ST_HANDLER_EVENT_STREAM_BROWSE, st_bookmarks_stream_browse_cb, NULL);

  st_handler_add_field(st_bookmarks_handler,
		       st_handler_field_new(FIELD_HANDLER_NAME,
					    _("STHandler"),
					    G_TYPE_STRING,
					    0));

  st_handler_add_field(st_bookmarks_handler,
		       st_handler_field_new(FIELD_STREAM_BAG,
					    _("STStreamBag"),
					    G_TYPE_VALUE_ARRAY,
					    0));

  st_handler_add_field(st_bookmarks_handler,
		       st_handler_field_new(FIELD_HANDLER_ICON,
					    _("Handler icon"),
					    GDK_TYPE_PIXBUF,
					    ST_HANDLER_FIELD_VISIBLE
					    | ST_HANDLER_FIELD_VOLATILE
					    | ST_HANDLER_FIELD_NO_DEDICATED_COLUMN));
  
  field = st_handler_field_new(FIELD_HANDLER_LABEL,
			       _("Handler"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_VOLATILE);
  st_handler_field_set_description(field, _("The stream handler"));
  st_handler_add_field(st_bookmarks_handler, field);

  field = st_handler_field_new(FIELD_NAME,
			       _("Name"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_EDITABLE);
  st_handler_field_set_description(field, _("The stream name"));
  st_handler_add_field(st_bookmarks_handler, field);

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

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

  field = st_handler_field_new(FIELD_HOMEPAGE,
			       _("Homepage"),
			       G_TYPE_STRING,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_START_HIDDEN
			       | ST_HANDLER_FIELD_VOLATILE);
  st_handler_field_set_description(field, _("The stream homepage URL"));
  st_handler_add_field(st_bookmarks_handler, field);

  field = st_handler_field_new(FIELD_URI_LIST,
			       _("URI list"),
			       G_TYPE_VALUE_ARRAY,
			       ST_HANDLER_FIELD_VISIBLE
			       | ST_HANDLER_FIELD_START_HIDDEN
			       | ST_HANDLER_FIELD_VOLATILE);
  st_handler_field_set_description(field, _("The stream listen URI list"));
  st_handler_add_field(st_bookmarks_handler, field);

  st_handlers_add(st_bookmarks_handler);

  bookmarks = st_stream_store_new(st_bookmarks_handler);
  st_handler_set_streams(st_bookmarks_handler, ST_CATEGORY_BAG_MAIN, bookmarks);
  g_object_unref(bookmarks);
}

static void
st_bookmarks_make_unique_name (STStreamBag *bag, STStreamStore *bookmarks)
{
  MakeUniqueInfo info = { bag };
  int i = 0;

  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(ST_IS_STREAM_STORE(bookmarks));
  
  do
    {
      g_free(ST_STREAM(bag)->name);
      ST_STREAM(bag)->name = g_strdup_printf("%i", i++);

      info.taken = FALSE;
      st_stream_store_foreach(bookmarks, st_bookmarks_make_unique_name_cb, &info);
    }
  while (info.taken);
}

static gboolean
st_bookmarks_make_unique_name_cb (STStreamStore *store,
				  STStreamBag *bag,
				  gpointer data)
{
  MakeUniqueInfo *info = data;
  return info->taken = ! strcmp(ST_STREAM(bag)->name, ST_STREAM(info->bag)->name);
}

void
st_bookmarks_add (STStreamBag *stream_bag)
{
  STStreamStore *bookmarks;
  STStreamBag *bookmark;

  bookmarks = st_handler_get_streams(st_bookmarks_handler, ST_CATEGORY_BAG_MAIN);
  g_return_if_fail(bookmarks != NULL);

  bookmark = st_stream_bag_new(st_bookmarks_handler);
  st_bookmarks_make_unique_name(bookmark, bookmarks);

  st_bookmarks_stream_set_handler(ST_BOOKMARKS_STREAM(bookmark), stream_bag->handler);
  if (st_handler_event_is_bound(stream_bag->handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET))
    {
      GValue value = { 0, };
      
      st_stream_bag_get_stock_field(stream_bag, ST_HANDLER_STOCK_FIELD_NAME, &value);
      ST_BOOKMARKS_STREAM(bookmark)->name = g_value_dup_string(&value);
      g_value_unset(&value);

      st_stream_bag_get_stock_field(stream_bag, ST_HANDLER_STOCK_FIELD_GENRE, &value);
      ST_BOOKMARKS_STREAM(bookmark)->genre = g_value_dup_string(&value);
      g_value_unset(&value);

      st_stream_bag_get_stock_field(stream_bag, ST_HANDLER_STOCK_FIELD_DESCRIPTION, &value);
      ST_BOOKMARKS_STREAM(bookmark)->description = g_value_dup_string(&value);
      g_value_unset(&value);
    }
  else
    {
      GString *name = g_string_new(NULL);
      GSList *l;

      /* we form the name by concatenating every visible string field */

      SG_LIST_FOREACH(l, st_handler_get_fields(stream_bag->handler))
        {
	  STHandlerField *field = l->data;

	  if (ST_HANDLER_FIELD_IS_VISIBLE(field) && st_handler_field_get_type(field) == G_TYPE_STRING)
	    {
	      GValue value = { 0, };
	      const char *str;

	      st_stream_bag_get_field(stream_bag, field, &value);
	      str = g_value_get_string(&value);
	      
	      if (str && *str)
		{
		  if (*name->str)
		    g_string_append(name, " - ");
		  g_string_append(name, str);
		}

	      g_value_unset(&value);
	    }
	}
      
      ST_BOOKMARKS_STREAM(bookmark)->name = g_string_free(name, FALSE);
    }
  
  ST_BOOKMARKS_STREAM(bookmark)->stream_bag = g_object_ref(stream_bag);

  st_stream_store_append(bookmarks, bookmark);
  st_stream_store_touch(bookmarks);

  g_object_unref(bookmark);
  g_object_unref(bookmarks);
}
