/*
 * 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 <string.h>
#include <glib-object.h>
#include "sg-util.h"
#include "st-stream-bag.h"
#include "st-stream-view.h"
#include "st-handler.h"
#include "st-stock.h"
#include "st-stream-store.h"
#include "st-handler-field.h"

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

enum {
  CHANGED,
  STATE_CHANGED,
  DELETED,
  LAST_SIGNAL
};
    
struct _STStreamBagPrivate
{
  int			running_count;
};

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

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

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

static void st_stream_bag_class_init (STStreamBagClass *class);
static void st_stream_bag_init (STStreamBag *bag);
static void st_stream_bag_finalize (GObject *object);

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

GType
st_stream_bag_get_type (void)
{
  static GType stream_bag_type = 0;
  
  if (! stream_bag_type)
    {
      static const GTypeInfo stream_bag_info = {
	sizeof(STStreamBagClass),
	NULL,
	NULL,
	(GClassInitFunc) st_stream_bag_class_init,
	NULL,
	NULL,
	sizeof(STStreamBag),
	0,
	(GInstanceInitFunc) st_stream_bag_init,
      };
      
      stream_bag_type = g_type_register_static(G_TYPE_OBJECT,
					       "STStreamBag",
					       &stream_bag_info,
					       0);
    }

  return stream_bag_type;
}

static void
st_stream_bag_class_init (STStreamBagClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STStreamBagPrivate));

  object_class->finalize = st_stream_bag_finalize;

  stream_bag_signals[CHANGED] = g_signal_new("changed",
					     ST_TYPE_STREAM_BAG,
					     G_SIGNAL_RUN_LAST,
					     G_STRUCT_OFFSET(STStreamBagClass, changed),
					     NULL,
					     NULL,
					     g_cclosure_marshal_VOID__VOID,
					     G_TYPE_NONE,
					     0);
  stream_bag_signals[STATE_CHANGED] = g_signal_new("state-changed",
						   ST_TYPE_STREAM_BAG,
						   G_SIGNAL_RUN_LAST,
						   G_STRUCT_OFFSET(STStreamBagClass, state_changed),
						   NULL,
						   NULL,
						   g_cclosure_marshal_VOID__BOOLEAN,
						   G_TYPE_NONE,
						   1,
						   G_TYPE_BOOLEAN);
  stream_bag_signals[DELETED] = g_signal_new("deleted",
					     ST_TYPE_STREAM_BAG,
					     G_SIGNAL_RUN_LAST,
					     G_STRUCT_OFFSET(STStreamBagClass, deleted),
					     NULL,
					     NULL,
					     g_cclosure_marshal_VOID__VOID,
					     G_TYPE_NONE,
					     0);
}

static void
st_stream_bag_init (STStreamBag *bag)
{
  bag->stream = NULL;
  bag->handler = NULL;
  memset(&bag->iter, 0, sizeof(bag->iter));
  
  bag->priv = G_TYPE_INSTANCE_GET_PRIVATE(bag, ST_TYPE_STREAM_BAG, STStreamBagPrivate);
}

static void
st_stream_bag_finalize (GObject *object)
{
  STStreamBag *bag = ST_STREAM_BAG(object);

  st_handler_event_stream_free(bag->handler, bag->stream);

  parent_class->finalize(object);
}

STStreamBag *
st_stream_bag_new (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return st_stream_bag_new_from_stream(handler, st_handler_event_stream_new(handler));
}

STStreamBag *
st_stream_bag_new_from_stream (STHandler *handler, STStream *stream)
{
  STStreamBag *bag;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(stream != NULL, NULL);

  bag = g_object_new(ST_TYPE_STREAM_BAG, NULL);

  bag->stream = stream;
  bag->handler = handler;

  return bag;
}

void
st_stream_bag_running_ref (STStreamBag *bag)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));

  if (++bag->priv->running_count == 1)
    g_signal_emit(bag, stream_bag_signals[STATE_CHANGED], 0, TRUE);
}

void
st_stream_bag_running_unref (STStreamBag *bag)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(bag->priv->running_count > 0);

  if (--bag->priv->running_count == 0)
    g_signal_emit(bag, stream_bag_signals[STATE_CHANGED], 0, FALSE);
}

/*** event wrappers **********************************************************/

void
st_stream_bag_get_field (STStreamBag *bag,
			 STHandlerField *field,
			 GValue *value)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(field != NULL);
  g_return_if_fail(value != NULL);

  g_value_init(value, st_handler_field_get_type(field));
  st_handler_event_stream_field_get(bag->handler, bag->stream, field, value);
}

void
st_stream_bag_set_field (STStreamBag *bag,
			 STHandlerField *field,
			 const GValue *value)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(field != NULL);
  g_return_if_fail(G_IS_VALUE(value));

  st_handler_event_stream_field_set(bag->handler, bag->stream, field, value);
}

void
st_stream_bag_get_stock_field (STStreamBag *bag,
			       STHandlerStockField stock_field,
			       GValue *value)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET));

  switch (stock_field)
    {
    case ST_HANDLER_STOCK_FIELD_NAME:
    case ST_HANDLER_STOCK_FIELD_GENRE:
    case ST_HANDLER_STOCK_FIELD_DESCRIPTION:
    case ST_HANDLER_STOCK_FIELD_HOMEPAGE:
      g_value_init(value, G_TYPE_STRING);
      break;

    case ST_HANDLER_STOCK_FIELD_URI_LIST:
      g_value_init(value, G_TYPE_VALUE_ARRAY);
      break;

    default:
      g_return_if_reached();
    }
  
  st_stream_bag_get_stock_field_initialized(bag, stock_field, value);
}

void
st_stream_bag_get_stock_field_initialized (STStreamBag *bag,
					   STHandlerStockField stock_field,
					   GValue *value)
{
  g_return_if_fail(ST_IS_STREAM_BAG(bag));
  g_return_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET));
  g_return_if_fail(G_IS_VALUE(value));

  switch (stock_field)
    {
    case ST_HANDLER_STOCK_FIELD_NAME:
    case ST_HANDLER_STOCK_FIELD_GENRE:
    case ST_HANDLER_STOCK_FIELD_DESCRIPTION:
    case ST_HANDLER_STOCK_FIELD_HOMEPAGE:
      g_return_if_fail(G_VALUE_HOLDS_STRING(value));
      break;

    case ST_HANDLER_STOCK_FIELD_URI_LIST:
      g_return_if_fail(G_VALUE_HOLDS(value, G_TYPE_VALUE_ARRAY));
      break;

    default:
      g_return_if_reached();
    }
  
  st_handler_event_stream_stock_field_get(bag->handler, bag->stream, stock_field, value);
}

/*
 * Must be called from a thread.
 */
gboolean
st_stream_bag_resolve (STStreamBag *bag, GError **err)
{
  gboolean status;

  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_RESOLVE), FALSE);

  status = st_handler_event_stream_resolve(bag->handler, ST_STREAM(bag), err);
  if (status)
    {
      GDK_THREADS_ENTER();
      g_signal_emit(bag, stream_bag_signals[CHANGED], 0);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }

  return status;
}

/*
 * Must be called from a thread.
 */
gboolean
st_stream_bag_tune_in_multiple (GSList *bags, GError **err)
{
  STHandler *handler;
  GSList *l;
  GSList *streams = NULL;
  gboolean status;

  /* require at least one stream */
  g_return_val_if_fail(bags != NULL, FALSE);

  handler = ST_STREAM_BAG(bags->data)->handler;
  g_return_val_if_fail(st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE), FALSE);

  SG_LIST_FOREACH(l, bags)
    {
      STStreamBag *bag = ST_STREAM_BAG(l->data);
      streams = g_slist_append(streams, ST_STREAM(bag));
    }

  status = st_handler_event_stream_tune_in_multiple(handler, streams, err);
  g_slist_free(streams);

  if (status)
    {
      GDK_THREADS_ENTER();
      SG_LIST_FOREACH(l, bags)
	g_signal_emit(l->data, stream_bag_signals[CHANGED], 0);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }
  
  return status;
}

/*
 * Must be called from a thread.
 */
gboolean
st_stream_bag_tune_in (STStreamBag *bag, GError **err)
{
  gboolean status;

  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_TUNE_IN), FALSE);

  status = st_handler_event_stream_tune_in(bag->handler, ST_STREAM(bag), err);
  if (status)
    {
      GDK_THREADS_ENTER();
      g_signal_emit(bag, stream_bag_signals[CHANGED], 0);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }

  return status;
}

/*
 * Must be called from a thread.
 */
gboolean
st_stream_bag_record (STStreamBag *bag, GError **err)
{
  gboolean status;

  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_RECORD), FALSE);

  status = st_handler_event_stream_record(bag->handler, ST_STREAM(bag), err);
  if (status)
    {
      GDK_THREADS_ENTER();
      g_signal_emit(bag, stream_bag_signals[CHANGED], 0);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }

  return status;
}

/*
 * Must be called from a thread.
 */
gboolean
st_stream_bag_browse (STStreamBag *bag, GError **err)
{
  gboolean status;

  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_BROWSE), FALSE);

  status = st_handler_event_stream_browse(bag->handler, ST_STREAM(bag), err);
  if (status)
    {
      GDK_THREADS_ENTER();
      g_signal_emit(bag, stream_bag_signals[CHANGED], 0);
      gdk_flush();
      GDK_THREADS_LEAVE();
    }

  return status;
}

gboolean
st_stream_bag_modify (STStreamBag *bag,
		      GSList *fields,
		      GSList *values,
		      GError **err)
{
  gboolean status;
  
  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_MODIFY), FALSE);

  status = st_handler_event_stream_modify(bag->handler, bag->stream, fields, values, err);

  /* the stream might have been modified even if ! status */
  g_signal_emit(bag, stream_bag_signals[CHANGED], 0);

  return status;
}

gboolean
st_stream_bag_delete (STStreamBag *bag, GError **err)
{
  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(st_handler_event_is_bound(bag->handler, ST_HANDLER_EVENT_STREAM_DELETE), FALSE);

  if (st_handler_event_stream_delete(bag->handler, bag->stream, err))
    {
      g_signal_emit(bag, stream_bag_signals[DELETED], 0);
      return TRUE;
    }
  else
    return FALSE;
}

gboolean
st_stream_bag_matches (STStreamBag *bag,
		       const char *token,
		       gboolean case_sensitive)
{
  GSList *l;
  gboolean matches = FALSE;

  g_return_val_if_fail(ST_IS_STREAM_BAG(bag), FALSE);
  g_return_val_if_fail(token != NULL, FALSE);

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

      if (ST_HANDLER_FIELD_IS_VISIBLE(field))
	{
	  GValue value = { 0, };
	  GValue transformed = { 0, };

	  st_stream_bag_get_field(bag, field, &value);
	  g_value_init(&transformed, G_TYPE_STRING);

	  if (g_value_transform(&value, &transformed))
	    {
	      const char *str = g_value_get_string(&transformed);
	      if (str)
		matches = case_sensitive
		  ? sg_utf8_strcontains(str, token)
		  : sg_utf8_strcasecontains(str, token);
	    }

	  g_value_unset(&value);
	  g_value_unset(&transformed);

	  if (matches)
	    break;
	}
    }

  return matches;
}
