/*
 * SwamiguiTreeStorePatch.c - Patch tree store (for instruments).
 *
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA or point your web browser to http://www.gnu.org.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <libinstpatch/libinstpatch.h>

#include "SwamiguiTreeStorePatch.h"
#include "SwamiguiRoot.h"
#include <libswami/SwamiLog.h>


/* used by swamigui_tree_store_real_item_add to qsort child items by title */
typedef struct
{
  GObject *tree_parent;	/* tree parent object (primary sort field) */
  char *title;	/* text title (secondary sort field, for sorted types) */
  GObject *item;
} ChildSortBag;

/* Local Prototypes */

static void swamigui_tree_store_patch_class_init
  (SwamiguiTreeStorePatchClass *klass);
static IpatchItem *swamigui_tree_store_create_virtual_child
  (IpatchItem *container, GType virtual_child_type);
static IpatchItem *swamigui_tree_store_lookup_virtual_child
  (IpatchItem *container, GType virtual_child_type);
static void
swamigui_tree_store_patch_real_item_add (
  SwamiguiTreeStore *store, GObject *item,
  GtkTreeIter *sibling, GtkTreeIter *out_iter,
  GtkTreeIter *in_parent_iter,
  ChildSortBag *inbag);
static int title_sort_compar_func (const void *a, const void *b);
static gboolean
get_item_sort_info (SwamiguiTreeStore *store, GObject *item, GType item_type,
		    GObject *parent, GObject **out_tree_parent,
		    GtkTreeIter *out_parent_iter);
static gboolean
find_sibling_title_sort (SwamiguiTreeStore *store, GObject *item, char *title,
			 GtkTreeIter *parent_iter, GtkTreeIter *sibling_iter);
static gboolean
find_sibling_container_sort (SwamiguiTreeStore *store, GObject *item,
			     GObject *parent, GtkTreeIter *parent_iter,
			     GtkTreeIter *sibling_iter);

GType
swamigui_tree_store_patch_get_type (void)
{
  static GType obj_type = 0;

  if (!obj_type) {
    static const GTypeInfo obj_info =
      {
	sizeof (SwamiguiTreeStorePatchClass), NULL, NULL,
	(GClassInitFunc) swamigui_tree_store_patch_class_init, NULL, NULL,
	sizeof (SwamiguiTreeStorePatch), 0,
	(GInstanceInitFunc) NULL,
      };

    obj_type = g_type_register_static (SWAMIGUI_TYPE_TREE_STORE,
				       "SwamiguiTreeStorePatch",
				       &obj_info, 0);
  }

  return (obj_type);
}

static void
swamigui_tree_store_patch_class_init (SwamiguiTreeStorePatchClass *klass)
{
  SwamiguiTreeStoreClass *store_class = SWAMIGUI_TREE_STORE_CLASS (klass);

  store_class->item_add = swamigui_tree_store_patch_item_add;
  store_class->item_changed = swamigui_tree_store_patch_item_changed;
}

/**
 * swamigui_tree_store_patch_new:
 *
 * Create a new patch tree store for instruments.
 *
 * Returns: New patch tree store object with a ref count of 1.
 */
SwamiguiTreeStorePatch *
swamigui_tree_store_patch_new (void)
{
  return (SWAMIGUI_TREE_STORE_PATCH
	  (g_object_new (SWAMIGUI_TYPE_TREE_STORE_PATCH, NULL)));
}

/*
 * swamigui_tree_store_create_virtual_child:
 * @container: Item to create virtual container child instance in
 * @virtual_child_type: The virtual child type
 *
 * Create a virtual container instance in a container. If @container already
 * has the specified @virtual_child_type it is simply returned.
 *
 * Returns: Virtual container instance
 */
static IpatchItem *
swamigui_tree_store_create_virtual_child (IpatchItem *container,
					  GType virtual_child_type)
{
  IpatchItem *virt_container;
  gpointer data;
  const char *keyname;

  g_return_val_if_fail (IPATCH_IS_ITEM (container), NULL);
  g_return_val_if_fail (g_type_is_a (virtual_child_type, IPATCH_TYPE_VIRTUAL_CONTAINER),
			NULL);

  /* use the virtual type name as the GObject data key */
  keyname = g_type_name (virtual_child_type);
  data = g_object_get_data (G_OBJECT (container), keyname);

  if (data) return (IPATCH_ITEM (data));

  virt_container = g_object_new (virtual_child_type, NULL);	/* ++ ref */
  ipatch_item_set_parent (IPATCH_ITEM (virt_container),IPATCH_ITEM (container));

  g_object_set_data (G_OBJECT (container), keyname, virt_container);

  /* !! Reference is held until @container is removed from tree */

  return (virt_container);
}

/*
 * swamigui_tree_store_lookup_virtual_child:
 * @container: Container item to look up a virtual child instance in
 * @virtual_child_type: The virtual child type
 *
 * Lookup a virtual container child instance in a container.
 *
 * Returns: Virtual container instance or %NULL if no virtual child of
 *   requested type.
 */
static IpatchItem *
swamigui_tree_store_lookup_virtual_child (IpatchItem *container,
					  GType virtual_child_type)
{
  gpointer data;
  const char *keyname;

  g_return_val_if_fail (IPATCH_IS_ITEM (container), NULL);
  g_return_val_if_fail (g_type_is_a (virtual_child_type, IPATCH_TYPE_VIRTUAL_CONTAINER),
			NULL);

  /* use the virtual type name as the GObject data key */
  keyname = g_type_name (virtual_child_type);
  data = g_object_get_data (G_OBJECT (container), keyname);

  if (data) return (IPATCH_ITEM (data));
  else return (NULL);
}

/**
 * swamigui_tree_store_patch_item_add:
 * @store: Patch tree store
 * @item: Item to add
 *
 * Function used as item_add method of #SwamiguiTreeStorePatchClass.
 * Might be useful to other tree store types.
 */
void
swamigui_tree_store_patch_item_add (SwamiguiTreeStore *store, GObject *item)
{
  swamigui_tree_store_patch_real_item_add (store, item, NULL, NULL, NULL, NULL);
}

/* some tricks are done to speed up adding a container item (children are
   pre-sorted to decrease exponential list iterations). Yeah looks pretty
   ugly, but in theory it should provide a bit of a speedup. */
static void
swamigui_tree_store_patch_real_item_add (
  SwamiguiTreeStore *store, GObject *item,
  GtkTreeIter *sibling, GtkTreeIter *out_iter,
  GtkTreeIter *in_parent_iter,
  ChildSortBag *inbag)
{
  GtkTreeIter *parent_iter = NULL, real_parent_iter;
  GtkTreeIter sibling_iter, item_iter, *pitem_iter, tmp_iter;
  GObject *parent, *childitem;
  GObject *tree_parent;
  IpatchList *list;
  ChildSortBag bag, *bagp;
  char *alloc_title = NULL, *title;
  gboolean sort = FALSE;
  GArray *title_sort_array = NULL;
  GHashTable *prev_child_hash = NULL;
  GList *p;
  int i;

  /* ++ ref parent */
  parent = (GObject *)ipatch_item_get_parent (IPATCH_ITEM (item));
  g_return_if_fail (parent != NULL);

  if (inbag)	/* recursive call? - Use already fetched values. */
    {
      title = inbag->title;
      tree_parent = inbag->tree_parent;
      parent_iter = in_parent_iter;
    }
  else		/* not a recursive call, no pre-cached values */
    {
      gboolean sibling_set;

      /* get title of item (if not supplied to function) */
      g_object_get (item, "title", &title, NULL);
      if (swami_log_if_fail (title != NULL)) goto ret;

      alloc_title = title;

      /* get sort boolean, tree parent object and node */
      sort = get_item_sort_info (store, item, 0, parent,
				 &tree_parent, &real_parent_iter);
      if (tree_parent) parent_iter = &real_parent_iter;

      if (sort)
	sibling_set = find_sibling_title_sort (store, item, title,
					       parent_iter, &sibling_iter);
      else sibling_set = find_sibling_container_sort (store, item, parent,
						    parent_iter, &sibling_iter);
      if (sibling_set) sibling = &sibling_iter;
    }	/* else - !inbag */

  /* insert the node into the tree */
  swamigui_tree_store_insert_after (store, item, title, NULL,
				    parent_iter, sibling, &item_iter);

  if (out_iter) *out_iter = item_iter;

  /* is added item a container? */
  if (IPATCH_IS_CONTAINER (item))
    {
      const GType *types;

      types = ipatch_container_get_virtual_types (IPATCH_CONTAINER (item));
      if (types)	/* any virtual container child types? */
	{
	  IpatchItem *virt;

	  for (; *types; types++)	/* create virtual containers */
	    {
	      virt = swamigui_tree_store_create_virtual_child
		  (IPATCH_ITEM (item), *types);

	      swamigui_tree_store_insert_before (store, G_OBJECT (virt),
				  NULL, NULL, &item_iter, NULL, NULL);
	    }
	}

      types = ipatch_container_get_child_types (IPATCH_CONTAINER (item));
      if (!types) goto ret;	/* should always be child types, but.. */

      title_sort_array = g_array_new (FALSE, FALSE, sizeof (ChildSortBag));
      prev_child_hash = g_hash_table_new_full (NULL, NULL,
					       NULL, (GDestroyNotify)g_free);

      for (; *types; types++)	/* loop over container child types */
	{
	  gboolean static_parent;

	  list = ipatch_container_get_children (IPATCH_CONTAINER (item),
						*types);  /* ++ ref list */
	  parent_iter = NULL;

	  /* virtual parent type is static? */
	  if (!ipatch_type_get_dynamic_func (*types, "virtual-parent-type"))
	    {
	      sort = get_item_sort_info (store, NULL, *types, item,
					 &tree_parent, &real_parent_iter);
	      if (tree_parent) parent_iter = &real_parent_iter;
	      static_parent = TRUE;
	    }
	  else static_parent = FALSE;

	  sibling = NULL;

	  /* loop over child items of the current type */
	  for (p = list->items; p; p = p->next)
	    {
	      childitem = (GObject *)(p->data);
	      /* dynamic virtual parent type? */
	      if (!static_parent)
		{
		  sort = get_item_sort_info (store, childitem, 0,
					     item, &tree_parent,
					     &real_parent_iter);
		  if (tree_parent) parent_iter = &real_parent_iter;
		}

	      /* store item's values in the bag structure, may or may not get
		 added to the title sort array, depending on if child is
                 title sorted */
	      bag.tree_parent = tree_parent;

	      g_object_get (childitem, "title", &title, NULL);
	      if (swami_log_if_fail (title != NULL))
		{
		  g_object_unref (list);	/* -- unref list */
		  goto ret;
		}

	      bag.title = title;
	      bag.item = childitem;

	      if (sort)	/* item should be sorted by title? */
		{
		  g_array_append_val (title_sort_array, bag);
		}
	      else		/* not title sorted, just add in order */
		{ /* keep track of the last child for each parent */
		  pitem_iter = g_hash_table_lookup (prev_child_hash, tree_parent);

		  sibling = pitem_iter;	/* sibling set if previous item */

		  if (!pitem_iter)
		    {
		      pitem_iter = g_new (GtkTreeIter, 1);
		      g_hash_table_insert (prev_child_hash, tree_parent,
					   pitem_iter);
		    }

		  swamigui_tree_store_patch_real_item_add
		    (store, G_OBJECT (p->data), sibling, &tmp_iter,
		     parent_iter, &bag);

		  *pitem_iter = tmp_iter;
		  g_free (bag.title);
		}
	    }	/* for .. p in list */

	  g_object_unref (list);	/* -- unref list */

	  if (title_sort_array->len > 0)
	    {
	      GObject *prev_parent = NULL;

	      /* sort the items by parent object and title */
	      qsort (title_sort_array->data, title_sort_array->len,
		     sizeof (ChildSortBag), title_sort_compar_func);

	      /* loop over sorted items */
	      for (i = 0; i < title_sort_array->len; i++)
		{
		  bagp = &g_array_index (title_sort_array, ChildSortBag, i);

		  /* dynamic virtual parent type? */
		  if (!static_parent)
		    {
		      sort = get_item_sort_info (store, G_OBJECT (bagp->item), 0,
						 item, &tree_parent,
						 &real_parent_iter);
		      if (tree_parent) parent_iter = &real_parent_iter;
		    }

		  /* previous and current item are in different containers? */
		  if (prev_parent != tree_parent) sibling = NULL;
		  else sibling = &item_iter;

		  swamigui_tree_store_patch_real_item_add
		    (store, G_OBJECT (bagp->item), sibling, &tmp_iter,
		     parent_iter, bagp);

		  item_iter = tmp_iter;
		  prev_parent = tree_parent;
		  g_free (bagp->title);
		}

	      g_array_set_size (title_sort_array, 0);	/* clear array */
	    }  /* if (title_sort_array->len > 0) */
	}  /* for (*types) - container child types */
    }  /* if (IPATCH_IS_CONTAINER (item)) */

 ret:

  g_object_unref (parent);	/* -- unref parent */
  if (alloc_title) g_free (alloc_title);
  if (title_sort_array) g_array_free (title_sort_array, TRUE);
  if (prev_child_hash) g_hash_table_destroy (prev_child_hash);
}

static int
title_sort_compar_func (const void *a, const void *b)
{
  ChildSortBag *baga = (ChildSortBag *)a, *bagb = (ChildSortBag *)b;

  if (baga->tree_parent == bagb->tree_parent)
    return (strcmp (baga->title, bagb->title));

  /* parent object is the primary sort */
  if (baga->tree_parent < bagb->tree_parent)
    return (1);
  else return (-1);
}

/* a helper function to get sort-children property that applies to an @item
   (can be NULL if item_type is set instead) and can also get the tree parent
   object and iterator if pointers are supplied.
   Returns: TRUE if item should be title sorted, FALSE otherwise
*/
static gboolean
get_item_sort_info (SwamiguiTreeStore *store, GObject *item, GType item_type,
		    GObject *parent, GObject **out_tree_parent,
		    GtkTreeIter *out_parent_iter)
{
  GObject *tree_parent;
  gboolean sort, has_parent_iter;
  GType type;

  if (item) ipatch_type_object_get (item, "virtual-parent-type", &type, NULL);
  else ipatch_type_get (item_type, "virtual-parent-type", &type, NULL);

  if (out_tree_parent || out_parent_iter)
    { /* item has a virtual parent type? Get the instance of it. */
      if (type != G_TYPE_NONE)
	tree_parent = (GObject *)swamigui_tree_store_lookup_virtual_child
			(IPATCH_ITEM (parent), type);
      else if (SWAMI_IS_CONTAINER (parent)) /* NULL parent to append to root */
	tree_parent = NULL;
      else tree_parent = parent;  /* otherwise use real parent */

      if (tree_parent)
	{
	  /* get the tree node for the tree parent item */
	  has_parent_iter
	    = swamigui_tree_store_item_get_node (store, G_OBJECT (tree_parent),
						 out_parent_iter);
	  if (swami_log_if_fail (has_parent_iter))
	      return (FALSE);
	}

      *out_tree_parent = tree_parent;
    }

  /* use real parent type if no virtual type */
  if (type == G_TYPE_NONE && item) type = G_OBJECT_TYPE (item);

  /* children should be sorted? */
  if (type != G_TYPE_NONE) ipatch_type_get (type, "sort-children", &sort, NULL);
  else sort = FALSE;

  return (sort);
}

/* find the closest sibling node already in tree store to insert after, sorted
 *   by title.
 * Returns: TRUE if sibling_iter is set, FALSE if item should be first.
 */
static gboolean
find_sibling_title_sort (SwamiguiTreeStore *store, GObject *item, char *title,
			 GtkTreeIter *parent_iter, GtkTreeIter *sibling_iter)
{
  GtkTreeModel *model = GTK_TREE_MODEL (store);
  gboolean haveprev = FALSE;
  GtkTreeIter child;
  GObject *cmpitem;
  char *curtitle;

  /* parent has any children? */
  if (!gtk_tree_model_iter_children (model, &child, parent_iter))
    return (FALSE);

  do	/* search for sibling to insert before */
    {
      gtk_tree_model_get (model, &child,
                          SWAMIGUI_TREE_STORE_LABEL_COLUMN, &curtitle,  /* ++ alloc */
                          SWAMIGUI_TREE_STORE_OBJECT_COLUMN, &cmpitem,  /* ++ ref */
                          -1);

      g_object_unref (cmpitem); /* -- unref, we only need the pointer to compare */

      /* compare titles and make sure its not the same item (if called to re-sort item) */
      if (strcmp (title, curtitle) <= 0 && item != cmpitem)
	{
	  g_free (curtitle);
	  break;
	}

      g_free (curtitle);
      *sibling_iter = child;
      haveprev = TRUE;
    }
  while (gtk_tree_model_iter_next (model, &child));

  return (haveprev == TRUE);
}

/* find the closest sibling node already in tree store to insert after,
 *   sorted as found in container child list.
 * Returns: TRUE if sibling_iter is set, FALSE if item should be first.
 */
static gboolean
find_sibling_container_sort (SwamiguiTreeStore *store, GObject *item,
			     GObject *parent, GtkTreeIter *parent_iter,
			     GtkTreeIter *sibling_iter)
{
  GtkTreeModel *model = GTK_TREE_MODEL (store);
  GtkTreeIter tmpiter;
  GtkTreePath *parent_path = NULL, *sibparent_path;
  IpatchList *list;
  gboolean parent_match;
  const GType *childtypes;
  GType type;
  GList *p;

  /* Find matching child type which item is a decendent of - or use item's type */
  childtypes = ipatch_container_get_child_types (IPATCH_CONTAINER (parent));

  type = G_OBJECT_TYPE (item);

  for (; *childtypes; childtypes++)
  {
    if (g_type_is_a (type, *childtypes))
    {
      type = *childtypes;
      break;
    }
  }

  /* ++ ref list */
  list = ipatch_container_get_children (IPATCH_CONTAINER (parent), type);

  p = g_list_find (list->items, item);		/* find item in list */
  if (p)
    {	/* search for previous sibling already in tree */
      for (p = p->prev; p; p = p->prev)
	{ /* item in tree? */
	  if (swamigui_tree_store_item_get_node (store, p->data,
						 sibling_iter))
	    { /* Make sure its the same parent (no parent if child of root) */
	      if (gtk_tree_model_iter_parent (model, &tmpiter, sibling_iter))
		{
		  if (!parent_path)	/* get path of parent iterator in tree store */
		    parent_path = gtk_tree_model_get_path (model, parent_iter);

		  sibparent_path = gtk_tree_model_get_path (model, &tmpiter);

		  /* is item really a sibling (same parent node?) */
		  parent_match = gtk_tree_path_compare (parent_path,
							sibparent_path) == 0;
		  gtk_tree_path_free (sibparent_path);

		  if (parent_match) break;
		}
	      else break;	/* no parent in tree (child of root) */
	    }
	}
    }

  if (parent_path) gtk_tree_path_free (parent_path);

  g_object_unref (list);	/* -- unref list */

  return (p != NULL);
}

/**
 * swamigui_tree_store_patch_item_changed:
 * @store: Patch tree store
 * @item: Item that changed
 *
 * Function used as item_changed method of #SwamiguiTreeStorePatchClass.
 * Might be useful to other tree store types.
 */
void
swamigui_tree_store_patch_item_changed (SwamiguiTreeStore *store, GObject *item)
{
  GtkTreeIter item_iter, parent_iter, curparent_iter, sibling_iter;
  gboolean found_item_node;
  gboolean found_parent_node;
  GObject *parent = NULL, *curparent = NULL, *tree_parent;
  char *title, *curtitle = NULL;

  /* get title of item */
  g_object_get (item, "title", &title, NULL);   /* ++ alloc */
  if (!title) title = g_strdup ("");

  found_item_node = swamigui_tree_store_item_get_node (store, item, &item_iter);
  if (swami_log_if_fail (found_item_node)) goto ret;

  gtk_tree_model_get (GTK_TREE_MODEL (store), &item_iter,
                      SWAMIGUI_TREE_STORE_LABEL_COLUMN, &curtitle,  /* ++ alloc */
                      -1);
  if (strcmp (title, curtitle) == 0) goto ret;  /* No change in title? - Return */

  swamigui_tree_store_change (store, item, title, NULL);

  parent = (GObject *)ipatch_item_get_parent (IPATCH_ITEM (item));      /* ++ ref */
  if (swami_log_if_fail (parent != NULL)) goto ret;

  /* Is item under a sorted parent? */
  if (get_item_sort_info (store, item, 0, parent, &tree_parent, &parent_iter))
  {
    /* Check if parent node has changed (switched from melodic to percussion for example) */
    found_parent_node = gtk_tree_model_iter_parent (GTK_TREE_MODEL (store),
                                                    &curparent_iter, &item_iter);
    if (swami_log_if_fail (found_parent_node)) goto ret;

    gtk_tree_model_get (GTK_TREE_MODEL (store), &curparent_iter,
                        SWAMIGUI_TREE_STORE_OBJECT_COLUMN, &curparent,  /* ++ ref */
                        -1);

    /* If the parent is the same, we can use the tree move functions */
    if (curparent == tree_parent)
    {
      if (find_sibling_title_sort (store, item, title, &parent_iter, &sibling_iter))
        swamigui_tree_store_move_after (store, item, &sibling_iter);
      else swamigui_tree_store_move_after (store, item, NULL);
    }
    else        /* Parent changed, remove and add it back (yeah, PITA!) */
    {
      swamigui_tree_store_remove (store, item);
      swamigui_tree_store_patch_item_add (store, item);
    }
  }

ret:
  g_free (title);       /* -- free title */
  g_free (curtitle);    /* -- free curtitle */
  if (parent) g_object_unref (parent);  /* -- unref parent */
  if (curparent) g_object_unref (curparent);    /* -- unref curparent */
}
