/*
 * 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 "sg-util.h"
#include "sgtk-util.h"
#include "st-category-store.h"
#include "st-stock.h"
#include "st-handler.h"

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

enum {
  CATEGORY_APPENDED,
  LAST_SIGNAL
};

struct _STCategoryStorePrivate
{
  GNode		*stock_categories;
};

typedef struct
{
  STCategoryStore	*store;
  gboolean		has_selection;
  STCategoryBag		*main_category;
} MergeInfo;

typedef struct
{
  STCategoryStoreForeachCallback	cb;
  gpointer				data;
} ForeachInfo;

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

static unsigned int category_store_signals[LAST_SIGNAL] = { 0 };

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

static void st_category_store_class_init (STCategoryStoreClass *class);
static void st_category_store_init (STCategoryStore *store);

static void st_category_store_update_icon (STCategoryStore *store,
					   STCategoryBag *bag);

static void st_category_store_weak_notify (gpointer data,
					   GObject *former_store);
static void st_category_store_category_weak_notify (gpointer data,
						    GObject *former_bag);

static void st_category_store_notify_task_thread_h (GObject *object,
						    GParamSpec *spec,
						    gpointer user_data);

static gboolean st_category_store_append_node_cb (GNode *node, gpointer data);

static gboolean st_category_store_merge_node_cb (GNode *node, gpointer data);
static gboolean st_category_store_merge_node_foreach_cb (GtkTreeModel *model,
							 GtkTreePath *path,
							 GtkTreeIter *iter,
							 gpointer data);
static gboolean st_category_store_merge_node_stock_cb (GNode *node,
						       gpointer data);

static gboolean st_category_store_foreach_cb (GtkTreeModel *model,
					      GtkTreePath *path,
					      GtkTreeIter *iter,
					      gpointer data);

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

GType
st_category_store_get_type (void)
{
  static GType category_store_type = 0;
  
  if (! category_store_type)
    {
      static const GTypeInfo category_store_info = {
	sizeof(STCategoryStoreClass),
	NULL,
	NULL,
	(GClassInitFunc) st_category_store_class_init,
	NULL,
	NULL,
	sizeof(STCategoryStore),
	0,
	(GInstanceInitFunc) st_category_store_init,
      };
      
      category_store_type = g_type_register_static(GTK_TYPE_TREE_STORE,
						   "STCategoryStore",
						   &category_store_info,
						   0);
    }

  return category_store_type;
}

static void
st_category_store_class_init (STCategoryStoreClass *class)
{
  g_type_class_add_private(class, sizeof(STCategoryStorePrivate));

  category_store_signals[CATEGORY_APPENDED] = g_signal_new("category-appended",
							   ST_TYPE_CATEGORY_STORE,
							   G_SIGNAL_RUN_LAST,
							   G_STRUCT_OFFSET(STCategoryStoreClass, category_appended),
							   NULL,
							   NULL,
							   g_cclosure_marshal_VOID__OBJECT,
							   G_TYPE_NONE,
							   1,
							   ST_TYPE_CATEGORY_BAG);
}

static void
st_category_store_init (STCategoryStore *store)
{
  store->priv = G_TYPE_INSTANCE_GET_PRIVATE(store, ST_TYPE_CATEGORY_STORE, STCategoryStorePrivate);
}

STCategoryStore *
st_category_store_new (GNode *stock_categories)
{
  STCategoryStore *store;
  GType types[] = {
    ST_TYPE_CATEGORY_BAG,
    G_TYPE_STRING,
    G_TYPE_STRING,
    G_TYPE_STRING,
    G_TYPE_STRING
  };

  g_return_val_if_fail(stock_categories != NULL, NULL);

  store = g_object_new(ST_TYPE_CATEGORY_STORE, NULL);
  gtk_tree_store_set_column_types(GTK_TREE_STORE(store),
				  ST_CATEGORY_STORE_N_COLUMNS,
				  types);

  /* make countable (before we start appending categories) */

  sgtk_tree_model_make_countable(GTK_TREE_MODEL(store));

  /* we ship the store with stock categories already appended */

  store->priv->stock_categories = stock_categories;
  st_category_store_append_node(store, store->priv->stock_categories);

  return store;
}

void
st_category_store_append (STCategoryStore *store,
			  STCategoryBag *bag,
			  STCategoryBag *parent)
{
  GtkTreeIter iter;

  g_return_if_fail(ST_IS_CATEGORY_STORE(store));
  g_return_if_fail(ST_IS_CATEGORY_BAG(bag));

  gtk_tree_store_append(GTK_TREE_STORE(store), &iter, parent ? &parent->iter : NULL);
  bag->iter = iter;

  gtk_tree_store_set(GTK_TREE_STORE(store), &iter,
		     ST_CATEGORY_STORE_COLUMN_BAG, bag,
		     ST_CATEGORY_STORE_COLUMN_LABEL, ST_CATEGORY(bag)->label,
		     -1);

  st_category_store_update_icon(store, bag);

  g_signal_connect(bag, "notify::task-thread", G_CALLBACK(st_category_store_notify_task_thread_h), store);

  g_object_weak_ref(G_OBJECT(store), st_category_store_weak_notify, bag);
  g_object_weak_ref(G_OBJECT(bag), st_category_store_category_weak_notify, store);

  g_signal_emit(store, category_store_signals[CATEGORY_APPENDED], 0, bag);
}

static void
st_category_store_weak_notify (gpointer data, GObject *former_store)
{
  STCategoryBag *bag = data;

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

  g_signal_handlers_disconnect_by_func(bag, st_category_store_notify_task_thread_h, former_store);
}

static void
st_category_store_category_weak_notify (gpointer data, GObject *former_bag)
{
  STCategoryStore *store = data;

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

static void
st_category_store_update_icon (STCategoryStore *store, STCategoryBag *bag)
{
  STThread *task_thread;
  const char *stock_id_closed;
  const char *stock_id_open;

  g_return_if_fail(ST_IS_CATEGORY_STORE(store));
  g_return_if_fail(ST_IS_CATEGORY_BAG(bag));

  task_thread = st_category_bag_get_task_thread(bag);

  if (ST_CATEGORY_BAG_IS(bag, ST_CATEGORY_BAG_MAIN))
    stock_id_closed = task_thread ? ST_STOCK_MAIN_CATEGORY_RUNNING : ST_STOCK_MAIN_CATEGORY;
  else if (ST_CATEGORY_BAG_IS(bag, ST_CATEGORY_BAG_SEARCH))
    stock_id_closed = task_thread ? ST_STOCK_SEARCH_CATEGORY_RUNNING : ST_STOCK_SEARCH_CATEGORY;
  else
    stock_id_closed = task_thread ? ST_STOCK_CATEGORY_RUNNING : ST_STOCK_CATEGORY;

  stock_id_open = task_thread ? ST_STOCK_CATEGORY_OPEN_RUNNING : ST_STOCK_CATEGORY_OPEN;

  gtk_tree_store_set(GTK_TREE_STORE(store), &bag->iter,
		     /* used when is-expander is false */
		     ST_CATEGORY_STORE_COLUMN_STOCK_ID, stock_id_closed,
		     /* used when is-expander is true */
		     ST_CATEGORY_STORE_COLUMN_STOCK_ID_CLOSED, stock_id_closed,
		     ST_CATEGORY_STORE_COLUMN_STOCK_ID_OPEN, stock_id_open,
		     -1);
}

static void
st_category_store_notify_task_thread_h (GObject *object,
					GParamSpec *spec,
					gpointer user_data)
{
  STCategoryBag *bag = ST_CATEGORY_BAG(object);
  STCategoryStore *store = user_data;

  st_category_store_update_icon(store, bag);
}

void
st_category_store_append_node (STCategoryStore *store, GNode *node)
{
  g_return_if_fail(ST_IS_CATEGORY_STORE(store));
  g_return_if_fail(node != NULL);

  g_node_traverse(node,
		  G_PRE_ORDER,
		  G_TRAVERSE_ALL,
		  -1,
		  st_category_store_append_node_cb,
		  store);
}

static gboolean
st_category_store_append_node_cb (GNode *node, gpointer data)
{
  if (node->data)
    {
      STCategoryStore *store = data;
      STCategoryBag *bag = node->data;
      STCategoryBag *parent = node->parent && node->parent->data ? node->parent->data : NULL;

      st_category_store_append(store, bag, parent);
    }

  return FALSE;			/* continue */
}

/* do NOT use while a reload of the same tab is in progress */
void
st_category_store_merge_node (STCategoryStore *store, GNode *node)
{
  MergeInfo info = { store, FALSE, NULL };

  g_return_if_fail(ST_IS_CATEGORY_STORE(store));
  g_return_if_fail(node != NULL);

  g_node_traverse(node,
		  G_PRE_ORDER,
		  G_TRAVERSE_ALL,
		  -1,
		  st_category_store_merge_node_cb,
		  &info);

  if (! info.has_selection)
    {
      g_node_traverse(store->priv->stock_categories,
		      G_PRE_ORDER,
		      G_TRAVERSE_ALL,
		      -1,
		      st_category_store_merge_node_stock_cb,
		      &info);

      if (! info.has_selection)
	st_category_bag_select(info.main_category);
    }

  st_category_store_clear(store);
  st_category_store_append_node(store, node);
}

static gboolean
st_category_store_merge_node_cb (GNode *node, gpointer data)
{
  if (node->data)
    {
      MergeInfo *info = data;

      gtk_tree_model_foreach(GTK_TREE_MODEL(info->store),
			     st_category_store_merge_node_foreach_cb,
			     &node->data);

      if (ST_CATEGORY_BAG_IS_SELECTED((STCategoryBag *) node->data))
	info->has_selection = TRUE;
    }
  
  return FALSE;			/* continue */
}

static gboolean
st_category_store_merge_node_foreach_cb (GtkTreeModel *model,
					 GtkTreePath *path,
					 GtkTreeIter *iter,
					 gpointer data)
{
  STCategoryBag **bag = data;
  STCategoryBag *this_bag;
  gboolean status;

  gtk_tree_model_get(model, iter, ST_CATEGORY_STORE_COLUMN_BAG, &this_bag, -1);
  if (! ST_CATEGORY_BAG_IS_STOCK(this_bag)
      && ST_CATEGORY_BAG_IS(this_bag, ST_CATEGORY(*bag)->name))
    {
      st_handler_event_category_free(this_bag->handler, this_bag->category);
      this_bag->category = ST_CATEGORY(*bag);
      ST_CATEGORY(*bag) = NULL;

      g_object_unref(*bag);
      *bag = this_bag;

      return TRUE;		/* stop */
    }
  else
    {
      g_object_unref(this_bag);
      return FALSE;		/* continue */
    }

  return status;
}

static gboolean
st_category_store_merge_node_stock_cb (GNode *node, gpointer data)
{
  MergeInfo *info = data;

  if (node->data)
    {
      STCategoryBag *bag = node->data;

      if (ST_CATEGORY_BAG_IS_SELECTED(bag))
	info->has_selection = TRUE;
      else if (ST_CATEGORY_BAG_IS(bag, ST_CATEGORY_BAG_MAIN))
	info->main_category = bag;
    }

  return info->has_selection;
}

void
st_category_store_foreach (STCategoryStore *store,
			   STCategoryStoreForeachCallback cb,
			   gpointer data)
{
  ForeachInfo info = { cb, data };

  g_return_if_fail(ST_IS_CATEGORY_STORE(store));
  
  gtk_tree_model_foreach(GTK_TREE_MODEL(store),
			 st_category_store_foreach_cb,
			 &info);
}

static gboolean
st_category_store_foreach_cb (GtkTreeModel *model,
			      GtkTreePath *path,
			      GtkTreeIter *iter,
			      gpointer data)
{
  ForeachInfo *info = data;
  STCategoryBag *bag;
  GtkTreeIter parent_iter;
  STCategoryBag *parent_bag = NULL;
  gboolean status;

  gtk_tree_model_get(model, iter, ST_CATEGORY_STORE_COLUMN_BAG, &bag, -1);
  if (gtk_tree_model_iter_parent(model, &parent_iter, iter))
    gtk_tree_model_get(model, &parent_iter, ST_CATEGORY_STORE_COLUMN_BAG, &parent_bag, -1);

  status = info->cb(ST_CATEGORY_STORE(model), bag, parent_bag, info->data);

  g_object_unref(bag);
  if (parent_bag)
    g_object_unref(parent_bag);

  return status;
}

void
st_category_store_clear (STCategoryStore *store)
{
  g_return_if_fail(ST_IS_CATEGORY_STORE(store));

  gtk_tree_store_clear(GTK_TREE_STORE(store));
  st_category_store_append_node(store, store->priv->stock_categories);
}

gboolean
st_category_store_search_equal_func (GtkTreeModel *model,
				     int column,
				     const char *key,
				     GtkTreeIter *iter,
				     gpointer search_data)
{
  char *label;
  gboolean equal = FALSE;

  gtk_tree_model_get(model, iter, ST_CATEGORY_STORE_COLUMN_LABEL, &label, -1);
  equal = label && sg_utf8_strcasecontains(label, key);
  g_free(label);

  return ! equal;
}
