/*
 * 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 <glib/gi18n.h>
#include "sg-util.h"
#include "sgtk-util.h"
#include "st-stream-store.h"
#include "st-handler-field.h"
#include "st-handler.h"

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

struct _STStreamStorePrivate
{
  gboolean touched;
};

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

static GdkPixbuf *blank_pixbuf = NULL;

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

static void st_stream_store_class_init (STStreamStoreClass *class);
static void st_stream_store_init (STStreamStore *store);

static void st_stream_store_sort_column_changed_h (GtkTreeSortable *treesortable,
						   gpointer user_data);

static void st_stream_store_weak_notify (gpointer data, GObject *former_store);
static void st_stream_store_stream_weak_notify (gpointer data, GObject *former_bag);

static void st_stream_store_stream_changed_h (STStreamBag *bag,
					      gpointer user_data);
static void st_stream_store_stream_state_changed_h (STStreamBag *bag,
						    gboolean running,
						    gpointer user_data);
static void st_stream_store_stream_deleted_h (STStreamBag *bag,
					      gpointer user_data);

static void st_stream_store_update (STStreamStore *store, STStreamBag *bag);

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

GType
st_stream_store_get_type (void)
{
  static GType stream_store_type = 0;
  
  if (! stream_store_type)
    {
      static const GTypeInfo stream_store_info = {
	sizeof(STStreamStoreClass),
	NULL,
	NULL,
	(GClassInitFunc) st_stream_store_class_init,
	NULL,
	NULL,
	sizeof(STStreamStore),
	0,
	(GInstanceInitFunc) st_stream_store_init
      };
      
      stream_store_type = g_type_register_static(GTK_TYPE_LIST_STORE,
						 "STStreamStore",
						 &stream_store_info,
						 0);
    }

  return stream_store_type;
}

static void
st_stream_store_class_init (STStreamStoreClass *class)
{
  gboolean status;
  int width;
  int height;

  g_type_class_add_private(class, sizeof(STStreamStorePrivate));

  /* create a blank pixbuf for ST_STREAM_BAG_STATE_NORMAL */

  status = gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
  g_return_if_fail(status == TRUE);

  blank_pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, width, height);
  gdk_pixbuf_fill(blank_pixbuf, 0); /* fill with transparent black */
}

static void
st_stream_store_init (STStreamStore *store)
{
  store->priv = G_TYPE_INSTANCE_GET_PRIVATE(store, ST_TYPE_STREAM_STORE, STStreamStorePrivate);
}

STStreamStore *
st_stream_store_new (STHandler *handler)
{
  STStreamStore *store;
  GType *types;
  int i = ST_STREAM_STORE_N_STOCK_COLUMNS;
  GSList *fields;
  GSList *l;
  int sort_index;
  
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  store = g_object_new(ST_TYPE_STREAM_STORE, NULL);
  fields = st_handler_get_fields(handler);

  /* create the store */

  types = g_new(GType, ST_STREAM_STORE_N_STOCK_COLUMNS + g_slist_length(fields));
  types[ST_STREAM_STORE_COLUMN_BAG] = ST_TYPE_STREAM_BAG;
  types[ST_STREAM_STORE_COLUMN_STOCK_ID] = G_TYPE_STRING;
  types[ST_STREAM_STORE_COLUMN_PIXBUF] = GDK_TYPE_PIXBUF;

  SG_LIST_FOREACH(l, fields)
    {
      STHandlerField *field = l->data;

      if (ST_HANDLER_FIELD_IS_VISIBLE(field))
	{
	  GType type = st_handler_field_get_type(field);

	  /* keep in sync with st_stream_store_update() */
	  if (type == G_TYPE_BOOLEAN || type == G_TYPE_VALUE_ARRAY)
	    type = G_TYPE_STRING;
	    
	  types[i++] = type;
	}
    }

  gtk_list_store_set_column_types(GTK_LIST_STORE(store), i, types);
  g_free(types);

  /* set the sort column, if any */

  sort_index = st_handler_get_fields_sort_index(handler);
  if (sort_index)
    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store),
					 sort_index + ST_STREAM_STORE_N_STOCK_COLUMNS - 1, /* we substract 1 because 0 means "no sort column" */
					 st_handler_get_fields_sort_order(handler));

  /* bind signals */

  g_signal_connect(store,
		   "sort-column-changed",
		   G_CALLBACK(st_stream_store_sort_column_changed_h),
		   handler);

  sgtk_tree_model_make_countable(GTK_TREE_MODEL(store));

  return store;
}

static void
st_stream_store_sort_column_changed_h (GtkTreeSortable *treesortable,
				       gpointer user_data)
{
  int sort_column_id;
  GtkSortType order;

  if (gtk_tree_sortable_get_sort_column_id(treesortable, &sort_column_id, &order))
    {
      STHandler *handler = user_data;

      st_handler_set_fields_sort_index(handler, sort_column_id - ST_STREAM_STORE_N_STOCK_COLUMNS + 1); /* we add 1 because 0 means "no sort column" */
      st_handler_set_fields_sort_order(handler, order);
    }
}

void
st_stream_store_append (STStreamStore *store, STStreamBag *bag)
{
  GtkTreeIter iter;

  g_return_if_fail(ST_IS_STREAM_STORE(store));
  g_return_if_fail(ST_IS_STREAM_BAG(bag));

  gtk_list_store_append(GTK_LIST_STORE(store), &iter);
  bag->iter = iter;

  gtk_list_store_set(GTK_LIST_STORE(store), &iter, ST_STREAM_STORE_COLUMN_BAG, bag, -1);
  gtk_list_store_set(GTK_LIST_STORE(store), &iter, ST_STREAM_STORE_COLUMN_PIXBUF, blank_pixbuf, -1);
  st_stream_store_update(store, bag);

  g_object_connect(bag,
		   "signal::changed", st_stream_store_stream_changed_h, store,
		   "signal::state-changed", st_stream_store_stream_state_changed_h, store,
		   "signal::deleted", st_stream_store_stream_deleted_h, store,
		   NULL);

  g_object_weak_ref(G_OBJECT(store), st_stream_store_weak_notify, bag);
  g_object_weak_ref(G_OBJECT(bag), st_stream_store_stream_weak_notify, store);
}

static void
st_stream_store_weak_notify (gpointer data, GObject *former_store)
{
  STStreamBag *bag = data;

  g_object_weak_unref(G_OBJECT(bag), st_stream_store_stream_weak_notify, former_store);

  g_object_disconnect(bag,
		      "any-signal", st_stream_store_stream_changed_h, former_store,
		      "any-signal", st_stream_store_stream_state_changed_h, former_store,
		      "any-signal", st_stream_store_stream_deleted_h, former_store,
		      NULL);
}

static void
st_stream_store_stream_weak_notify (gpointer data, GObject *former_bag)
{
  STStreamStore *store = data;

  g_object_weak_unref(G_OBJECT(store), st_stream_store_weak_notify, former_bag);
}

static void
st_stream_store_stream_changed_h (STStreamBag *bag,
				  gpointer user_data)
{
  STStreamStore *store = user_data;

  st_stream_store_update(store, bag);
  st_stream_store_touch(store);
}

static void
st_stream_store_stream_state_changed_h (STStreamBag *bag,
					gboolean running,
					gpointer user_data)
{
  STStreamStore *store = user_data;

  gtk_list_store_set(GTK_LIST_STORE(store), &bag->iter,
		     ST_STREAM_STORE_COLUMN_STOCK_ID, running ? GTK_STOCK_EXECUTE : NULL,
		     ST_STREAM_STORE_COLUMN_PIXBUF, running ? NULL : blank_pixbuf,
		     -1);
}

static void
st_stream_store_stream_deleted_h (STStreamBag *bag,
				  gpointer user_data)
{
  STStreamStore *store = user_data;
  
  g_object_disconnect(bag,
		      "any-signal", st_stream_store_stream_changed_h, store,
		      "any-signal", st_stream_store_stream_state_changed_h, store,
		      "any-signal", st_stream_store_stream_deleted_h, store,
		      NULL);

  gtk_list_store_remove(GTK_LIST_STORE(store), &bag->iter);
  st_stream_store_touch(store);
}

void
st_stream_store_append_list (STStreamStore *store, GSList *list)
{
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_STORE(store));

  SG_LIST_FOREACH(l, list)
    st_stream_store_append(store, l->data);
}

static void
st_stream_store_update (STStreamStore *store, STStreamBag *bag)
{
  int i = ST_STREAM_STORE_N_STOCK_COLUMNS;
  GSList *l;

  g_return_if_fail(ST_IS_STREAM_STORE(store));
  g_return_if_fail(ST_IS_STREAM_BAG(bag));

  SG_LIST_FOREACH(l, st_handler_get_fields(bag->handler))
    {
      STHandlerField *field = l->data;
    
      if (ST_HANDLER_FIELD_IS_VISIBLE(field))
	{
	  GValue value = { 0, };

	  st_stream_bag_get_field(bag, field, &value);

	  if (G_VALUE_HOLDS_BOOLEAN(&value))
	    {
	      gboolean b = g_value_get_boolean(&value);

	      g_value_unset(&value);
	      g_value_init(&value, G_TYPE_STRING);
	      g_value_set_string(&value, b ? _("Yes") : _("No"));
	    }
	  else if (G_VALUE_HOLDS(&value, G_TYPE_VALUE_ARRAY))
	    {
	      GValueArray *value_array;
	      char *str;

	      value_array = g_value_get_boxed(&value);
	      str = value_array ? sg_value_array_get_string(value_array) : NULL;

	      g_value_unset(&value);
	      g_value_init(&value, G_TYPE_STRING);
	      g_value_take_string(&value, str);
	    }
	  
	  gtk_list_store_set_value(GTK_LIST_STORE(store), &bag->iter, i++, &value);
	  g_value_unset(&value);
	}
    }
}

void
st_stream_store_foreach (STStreamStore *store,
			 STStreamStoreForeachCallback cb,
			 gpointer data)
{
  GtkTreeIter iter;
  gboolean valid;

  g_return_if_fail(ST_IS_STREAM_STORE(store));
  
  valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
  while (valid)
    {
      STStreamBag *bag;
      gboolean status;

      gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, ST_STREAM_STORE_COLUMN_BAG, &bag, -1);
      status = cb(store, bag, data);
      g_object_unref(bag);

      if (status)
	break;

      valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
    }
}

gboolean
st_stream_store_search_equal_func (GtkTreeModel *model,
				   int column,
				   const char *key,
				   GtkTreeIter *iter,
				   gpointer search_data)
{
  STStreamBag *bag;
  gboolean matches;

  gtk_tree_model_get(model, iter, ST_STREAM_STORE_COLUMN_BAG, &bag, -1);
  matches = st_stream_bag_matches(bag, key, FALSE);
  g_object_unref(bag);

  return ! matches;
}

void
st_stream_store_touch (STStreamStore *store)
{
  g_return_if_fail(ST_IS_STREAM_STORE(store));

  store->priv->touched = TRUE;
}

gboolean
st_stream_store_is_touched (STStreamStore *store)
{
  g_return_val_if_fail(ST_IS_STREAM_STORE(store), FALSE);

  return store->priv->touched;
}
