/* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
/* Copyright (C) 2005 Carlos Garnacho
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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.
 *
 * Authors: Carlos Garnacho Parro  <carlosg@gnome.org>
 */

#include <dbus/dbus.h>
#include <glib-object.h>
#include <string.h>
#include "oobs-object.h"
#include "oobs-list.h"

#define OOBS_LIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), OOBS_TYPE_LIST, OobsListPrivate))

typedef struct _OobsListPrivate OobsListPrivate;

struct _OobsListPrivate
{
  GList *list;
  guint  stamp;

  GType  contained_type;
  gboolean locked;
};

static void oobs_list_class_init (OobsListClass *class);
static void oobs_list_init       (OobsList      *list);
static void oobs_list_finalize   (GObject      *object);

static void oobs_list_set_property (GObject      *object,
				    guint         prop_id,
				    const GValue *value,
				    GParamSpec   *pspec);
enum
{
  PROP_0,
  PROP_CONTAINED_TYPE
};

G_DEFINE_TYPE (OobsList, oobs_list, G_TYPE_OBJECT);

GType
oobs_list_iter_get_type (void)
{
  static GType iter_type = 0;
  
  if (iter_type == 0)
    iter_type = g_boxed_type_register_static ("OobsListIter",
					      (GBoxedCopyFunc) oobs_list_iter_copy,
					      (GBoxedFreeFunc) oobs_list_iter_free);

  return iter_type;
}

static void
oobs_list_class_init (OobsListClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->finalize = oobs_list_finalize;
  object_class->set_property = oobs_list_set_property;

  g_object_class_install_property (object_class,
				   PROP_CONTAINED_TYPE,
				   g_param_spec_pointer ("contained-type",
							 "Contained type",
							 "GType contained in the list",
							 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
  g_type_class_add_private (object_class,
			    sizeof (OobsListPrivate));
}

static void
oobs_list_init (OobsList *object)
{
  OobsListPrivate *priv;

  g_return_if_fail (OOBS_IS_LIST (object));
  priv = OOBS_LIST_GET_PRIVATE (object);

  priv->stamp  = 0;
  priv->list   = NULL;
  priv->locked = FALSE;
  object->_priv = priv;
}

static void
oobs_list_finalize (GObject *object)
{
  OobsList *list;
  OobsListPrivate *priv;

  g_return_if_fail (OOBS_IS_LIST (object));

  list = OOBS_LIST (object);
  priv = list->_priv;

  /* set locking to FALSE, the object
   * is already being finalized anyway */
  priv->locked = FALSE;

  if (priv && priv->list)
    oobs_list_clear (list);

  if (G_OBJECT_CLASS (oobs_list_parent_class)->finalize)
    (* G_OBJECT_CLASS (oobs_list_parent_class)->finalize) (object);
}

static void
oobs_list_set_property (GObject      *object,
			guint         prop_id,
			const GValue *value,
			GParamSpec   *pspec)
{
  OobsList *list;
  OobsListPrivate *priv;
  GType *type;

  g_return_if_fail (OOBS_IS_LIST (object));

  list = OOBS_LIST (object);
  priv = list->_priv;

  switch (prop_id)
    {
    case PROP_CONTAINED_TYPE:
      type = g_value_get_pointer (value);
      priv->contained_type = *type;
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static gboolean
check_iter (OobsListPrivate *priv, OobsListIter *iter)
{
  if (priv->stamp != iter->stamp)
    {
      g_critical ("OobsList stamp and OobsListIter stamp differ");
      return FALSE;
    }

  if (g_list_position (priv->list, iter->data) == -1)
    return FALSE;

  return TRUE;
}

GObject*
_oobs_list_new (const GType contained_type)
{
  return g_object_new (OOBS_TYPE_LIST,
		       "contained-type", &contained_type,
		       NULL);
}

void
_oobs_list_set_locked (OobsList *list, gboolean locked)
{
  OobsListPrivate *priv;

  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;
  priv->locked = locked;
}

/**
 * oobs_list_get_iter_first:
 * @list: An #OobsList
 * @iter: An uninitialized #OobsListIter
 * 
 * Initializes @iter the iterator pointing to the
 * first element in @list. if @list is empty, then FALSE
 * is returned, and @iter is not initialized.
 * 
 * Return Value: #TRUE if @iter was set
 **/
gboolean
oobs_list_get_iter_first (OobsList *list, OobsListIter *iter)
{
  OobsListPrivate *priv;

  g_return_val_if_fail (list != NULL, FALSE);
  g_return_val_if_fail (OOBS_IS_LIST (list), FALSE);

  priv = list->_priv;

  if (!priv->list)
    return FALSE;

  iter->stamp = priv->stamp;
  iter->data  = priv->list;

  return TRUE;
}

/**
 * oobs_list_iter_next:
 * @list: An #OobsList
 * @iter: A valid #OobsListIter pointing to an element in @list
 * 
 * Sets @iter to point to the element following it. If there's
 * no next @iter, of if @iter was invalid for @list, #FALSE is
 * returned, and @iter is set to invalid.
 * 
 * Return Value: #TRUE if iter has been correctly changed to the next element
 **/
gboolean
oobs_list_iter_next (OobsList *list, OobsListIter *iter)
{
  OobsListPrivate *priv;
  GList *data;

  g_return_val_if_fail (list != NULL, FALSE);
  g_return_val_if_fail (iter != NULL, FALSE);
  g_return_val_if_fail (iter->data != NULL, FALSE);
  g_return_val_if_fail (OOBS_IS_LIST (list), FALSE);

  priv = list->_priv;

  if (!check_iter (priv, iter))
    return FALSE;

  data = (GList *) iter->data;
  iter->data = data->next;

  return (iter->data != NULL);
}

/**
 * oobs_list_remove:
 * @list: An #OobsList
 * @iter: A valid #OobsListIter pointing to an element in @list
 * 
 * Removes an element pointed by @iter from @list. This function will not
 * be effective if @list is locked, or if @iter doesn't point to an element
 * contained in @list.
 * 
 * Return Value: #TRUE if the element was correctly removed
 **/
gboolean
oobs_list_remove (OobsList *list, OobsListIter *iter)
{
  OobsListPrivate *priv;
  GList *data, *next;
  gboolean list_locked;

  g_return_val_if_fail (list != NULL, FALSE);
  g_return_val_if_fail (iter != NULL, FALSE);
  g_return_val_if_fail (iter->data != NULL, FALSE);
  g_return_val_if_fail (OOBS_IS_LIST (list), FALSE);

  priv = list->_priv;

  list_locked = priv->locked;
  g_return_val_if_fail (list_locked != TRUE, FALSE);

  if (!check_iter (priv, iter))
    return FALSE;

  data = (GList *) iter->data;

  /* point to the next element */
  next = data->next;

  g_object_unref (data->data);
  priv->list = g_list_delete_link (priv->list, data);

  iter->data = next;

  return TRUE;
}

/**
 * oobs_list_append:
 * @list: An #OobsList
 * @iter: An unset #OobsListIter to set to the new element
 * 
 * Appends a new element to @list. @iter will be changed
 * to point to this element, to fill in values, you need to call
 * oobs_list_set().
 **/
void
oobs_list_append (OobsList *list, OobsListIter *iter)
{
  OobsListPrivate *priv;
  gboolean list_locked;

  g_return_if_fail (list != NULL);
  g_return_if_fail (iter != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;

  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  /* Change the stamp if the list was empty */
  if (!priv->list)
    priv->stamp++;

  priv->list = g_list_append (priv->list, NULL);

  iter->data = g_list_last (priv->list);
  iter->stamp = priv->stamp;
}

/**
 * oobs_list_prepend:
 * @list: An #OobsList
 * @iter: An unset #OobsListIter to set to the new element
 * 
 * Prepends a new element to @list. @iter will be changed
 * to point to this element, to fill in values, you need to call
 * oobs_list_set().
 **/
void
oobs_list_prepend (OobsList *list, OobsListIter *iter)
{
  OobsListPrivate *priv;
  gboolean list_locked;

  g_return_if_fail (list != NULL);
  g_return_if_fail (iter != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;

  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  /* Change the stamp if the list was empty */
  if (!priv->list)
    priv->stamp++;

  priv->list = g_list_prepend (priv->list, NULL);

  iter->data = priv->list;
  iter->stamp = priv->stamp;
}

/**
 * oobs_list_insert_after:
 * @list: An #OobsList
 * @anchor: A valid #OobsListIter
 * @iter: An unset #OobsListIter to set to the new element
 * 
 * Inserts a new element after @anchor. @iter will be changed
 * to point to this element, to fill in values, you need to call
 * oobs_list_set().
 **/
void
oobs_list_insert_after (OobsList     *list,
			OobsListIter *anchor,
			OobsListIter *iter)
{
  OobsListPrivate *priv;
  GList *node, *anchor_node;
  gboolean list_locked;

  g_return_if_fail (list != NULL);
  g_return_if_fail (anchor != NULL);
  g_return_if_fail (anchor->data != NULL);
  g_return_if_fail (iter != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;
  
  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  if (!check_iter (priv, anchor))
    return;

  anchor_node = anchor->data;
  node = g_list_alloc ();
  node->data = NULL;
  
  anchor_node->next->prev = node;
  node->next = anchor_node->next;
  anchor_node->next = node;
  node->prev = anchor_node;

  iter->stamp = priv->stamp;
  iter->data  = node;
}

/**
 * oobs_list_insert_before:
 * @list: An #OobsList
 * @anchor: A valid #OobsListIter
 * @iter: An unset #OobsListIter to set to the new element
 * 
 * Inserts a new element before @anchor. @iter will be changed
 * to point to this element, to fill in values, you need to call
 * oobs_list_set().
 **/
void
oobs_list_insert_before (OobsList     *list,
			 OobsListIter *anchor,
			 OobsListIter *iter)
{
  OobsListPrivate *priv;
  GList *node, *anchor_node;
  gboolean list_locked;

  /* FIXME: make it match with the
     gtk_list_store behavior and api */

  g_return_if_fail (list != NULL);
  g_return_if_fail (anchor != NULL);
  g_return_if_fail (anchor->data != NULL);
  g_return_if_fail (iter != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;
  
  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  if (!check_iter (priv, anchor))
    return;

  anchor_node = anchor->data;
  node = g_list_alloc ();
  node->data = NULL;

  anchor_node->prev->next = node;
  node->prev = anchor_node->prev;
  anchor_node->prev = node;
  node->next = anchor_node;

  iter->stamp = priv->stamp;
  iter->data  = node;
}

/**
 * oobs_list_get:
 * @list: an #OobsList
 * @iter: a valid #OobsListIter for the element being get
 * 
 * Retrieves a reference to the element
 * referenced by #OobsListIter.
 * 
 * Return Value: the element referenced by @iter
 **/
GObject*
oobs_list_get (OobsList     *list,
	       OobsListIter *iter)
{
  OobsListPrivate *priv;
  GList *node;

  g_return_val_if_fail (list != NULL, NULL);
  g_return_val_if_fail (iter != NULL, NULL);
  g_return_val_if_fail (iter->data != NULL, NULL);
  g_return_val_if_fail (OOBS_IS_LIST (list), NULL);

  node = iter->data;
  priv = list->_priv;

  g_return_val_if_fail (node->data != NULL, NULL);

  if (!check_iter (priv, iter))
    return NULL;

  return g_object_ref (node->data);
}

static gboolean
check_types (OobsList *list,
	     GObject  *data)
{
  OobsListPrivate *priv;

  priv = list->_priv;
  
  if (!G_TYPE_CHECK_INSTANCE_TYPE (data, priv->contained_type))
    {
      g_critical ("Trying to store a different object type in the list");
      return FALSE;
    }

  return TRUE;
}

/**
 * oobs_list_set:
 * @list: an #OobsList
 * @iter: a valid #OobsListIter for the element being set
 * @data: a pointer to the data being set
 * 
 * Sets the data for the element referenced by #OobsListIter.
 * This function will not be effective if the list is locked, or
 * if @data GType is different to the data contained in #OobsList.
 **/
void
oobs_list_set (OobsList     *list,
	       OobsListIter *iter,
	       gpointer      data)
{
  OobsListPrivate *priv;
  GList *node;
  gboolean list_locked;

  g_return_if_fail (list != NULL);
  g_return_if_fail (iter != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));
  g_return_if_fail (G_IS_OBJECT (data));

  node = iter->data;
  priv = list->_priv;

  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  g_return_if_fail (node->data == NULL);

  if (!check_iter (priv, iter))
    return;

  if (!check_types (list, data))
    return;

  node->data = g_object_ref (data);
}

/**
 * oobs_list_clear:
 * @list: an #OobsList
 * 
 * Removes all contents from an #OobsList. This function will
 * not be effective if the list is locked.
 **/
void
oobs_list_clear (OobsList *list)
{
  OobsListPrivate *priv;
  gboolean list_locked;
  
  g_return_if_fail (list != NULL);
  g_return_if_fail (OOBS_IS_LIST (list));

  priv = list->_priv;

  list_locked = priv->locked;
  g_return_if_fail (list_locked != TRUE);

  if (priv->list)
    {
      g_list_foreach (priv->list, (GFunc) g_object_unref, NULL);
      g_list_free    (priv->list);
      priv->list = NULL;
    }
}

/**
 * oobs_list_get_n_items:
 * @list: An #OobsList.
 * 
 * Returns the number of elements that the list contains.
 * 
 * Return Value: the number of elements.
 **/
gint
oobs_list_get_n_items (OobsList *list)
{
  OobsListPrivate *priv;

  g_return_val_if_fail (OOBS_IS_LIST (list), 0);

  priv = list->_priv;

  return g_list_length (priv->list);
}

/**
 * oobs_list_iter_copy:
 * @iter: An #OobsListIter.
 * 
 * Returns a newly allocated copy of the given iterator. This function is not
 * intended for use in applications, because you can just copy the structs by
 * value (OobsListIter new_iter = iter;). You must free this iter with
 * oobs_list_iter_free().
 * 
 * Return Value: A newly allocated iterator.
 **/
OobsListIter*
oobs_list_iter_copy (OobsListIter *iter)
{
  OobsListIter *copy;

  copy = g_new0 (OobsListIter, 1);
  copy->stamp = iter->stamp;
  copy->data  = iter->data;

  return copy;
}

/**
 * oobs_list_iter_free:
 * @iter: An #OobsListIter.
 * 
 * Frees an iterator that has been allocated in the heap.
 **/
void
oobs_list_iter_free (OobsListIter *iter)
{
  g_free (iter);
}


syntax highlighted by Code2HTML, v. 0.9.1