/* GStreamer
 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
 *                    2000 Wim Taymans <wim.taymans@chello.be>
 *
 * gstprops.c: Properties subsystem for generic usage
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* #define GST_DEBUG_ENABLED */
#include "gst_private.h"

#include "gstlog.h"
#include "gstprops.h"
#include "gstmemchunk.h"

GType _gst_props_type;
GType _gst_props_entry_type;

#define GST_PROPS_ENTRY_IS_VARIABLE(a)	(((GstPropsEntry*)(a))->propstype > GST_PROPS_VAR_TYPE)

struct _GstPropsEntry {
  GQuark    	propid;
  GstPropsType 	propstype;

  union {
    /* flat values */
    gboolean bool_data;
    guint32  fourcc_data;
    gint     int_data;
    gfloat   float_data;

    /* structured values */
    struct {
      GList *entries;
    } list_data;
    struct {
      gchar *string;
    } string_data;
    struct {
      gint min;
      gint max;
    } int_range_data;
    struct {
      gfloat min;
      gfloat max;
    } float_range_data;
  } data;
};

static GstMemChunk *_gst_props_entries_chunk;
static GstMemChunk *_gst_props_chunk;

static gboolean 	gst_props_entry_check_compatibility 	(GstPropsEntry *entry1, GstPropsEntry *entry2);
static GList* 		gst_props_list_copy 			(GList *propslist);

static void
transform_func (const GValue *src_value,
		GValue *dest_value)
{
  GstProps *props = g_value_peek_pointer (src_value);
  GString *result = g_string_new ("");

  if (props) {
    GList *propslist = props->properties;

    while (propslist) {
      GstPropsEntry *entry = (GstPropsEntry *)propslist->data;
      const gchar *name = g_quark_to_string (entry->propid);

      switch (entry->propstype) {
        case GST_PROPS_INT_TYPE:
  	  g_string_append_printf (result, "%s=(int) %d", name, entry->data.int_data);
	  break;
        case GST_PROPS_FLOAT_TYPE:
  	  g_string_append_printf (result, "%s=(float) %f", name, entry->data.float_data);
	  break;
        case GST_PROPS_FOURCC_TYPE:
  	  g_string_append_printf (result, "%s=(fourcc) '%4.4s'", name, (gchar *)&entry->data.fourcc_data);
	  break;
        case GST_PROPS_BOOLEAN_TYPE:
  	  g_string_append_printf (result, "%s=(boolean) %s", name, 
			  	  (entry->data.bool_data ? "TRUE" : "FALSE"));
	  break;
        case GST_PROPS_STRING_TYPE:
	  g_string_append_printf (result, "%s=(string) '%s'", name, entry->data.string_data.string);
	  break;
        default:
	  break;
      }

      propslist = g_list_next (propslist);
      if (propslist) {
        g_string_append (result, "; ");
      }
    }
  }
  dest_value->data[0].v_pointer = result->str;
  g_string_free (result, FALSE);
}

void
_gst_props_initialize (void)
{
  _gst_props_entries_chunk = gst_mem_chunk_new ("GstPropsEntries",
		  sizeof (GstPropsEntry), sizeof (GstPropsEntry) * 1024,
		  G_ALLOC_AND_FREE);

  _gst_props_chunk = gst_mem_chunk_new ("GstProps",
		  sizeof (GstProps), sizeof (GstProps) * 256,
		  G_ALLOC_AND_FREE);

  _gst_props_type = g_boxed_type_register_static ("GstProps",
		                       (GBoxedCopyFunc) gst_props_ref,
		                       (GBoxedFreeFunc) gst_props_unref);

  g_value_register_transform_func (_gst_props_type,
                                   G_TYPE_STRING,
                                   transform_func);

  _gst_props_entry_type = g_boxed_type_register_static ("GstPropsEntry",
	                    (GBoxedCopyFunc) gst_props_entry_copy,
	                    (GBoxedFreeFunc) gst_props_entry_destroy);
}

static void
gst_props_debug_entry (GstPropsEntry *entry)
{
  const gchar *name = g_quark_to_string (entry->propid);

  switch (entry->propstype) {
    case GST_PROPS_INT_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: int %d", entry, name, entry->data.int_data);
      break;
    case GST_PROPS_FLOAT_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: float %f", entry, name, entry->data.float_data);
      break;
    case GST_PROPS_FOURCC_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: fourcc %c%c%c%c", entry, name,
	(entry->data.fourcc_data>>0)&0xff,
	(entry->data.fourcc_data>>8)&0xff,
	(entry->data.fourcc_data>>16)&0xff,
	(entry->data.fourcc_data>>24)&0xff);
      break;
    case GST_PROPS_BOOLEAN_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: bool %d", entry, name, entry->data.bool_data);
      break;
    case GST_PROPS_STRING_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: string \"%s\"", entry, name, entry->data.string_data.string);
      break;
    case GST_PROPS_INT_RANGE_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: int range %d-%d", entry, name, entry->data.int_range_data.min,
		      entry->data.int_range_data.max);
      break;
    case GST_PROPS_FLOAT_RANGE_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: %s: float range %f-%f", entry, name, entry->data.float_range_data.min,
		      entry->data.float_range_data.max);
      break;
    case GST_PROPS_LIST_TYPE:
      GST_DEBUG (GST_CAT_PROPERTIES, "%p: [list]", entry);
      g_list_foreach (entry->data.list_data.entries, (GFunc) gst_props_debug_entry, NULL);
      break;
    default:
      g_warning ("unknown property type %d at %p", entry->propstype, entry);
      break;
  }
}

static gint
props_compare_func (gconstpointer a,
		    gconstpointer b)
{
  GstPropsEntry *entry1 = (GstPropsEntry *)a;
  GstPropsEntry *entry2 = (GstPropsEntry *)b;

  return (entry1->propid - entry2->propid);
}

static gint
props_find_func (gconstpointer a,
		 gconstpointer b)
{
  GstPropsEntry *entry2 = (GstPropsEntry *)a;
  GQuark quark = (GQuark) GPOINTER_TO_INT (b);

  return (quark - entry2->propid);
}

/* This is implemented as a huge macro because we cannot pass
 * va_list variables by reference on some architectures.
 */
#define GST_PROPS_ENTRY_FILL(entry, var_args)	 				\
G_STMT_START { 									\
  entry->propstype = va_arg (var_args, GstPropsType); 				\
										\
  switch (entry->propstype) {							\
    case GST_PROPS_INT_TYPE:							\
      entry->data.int_data = va_arg (var_args, gint);				\
      break;									\
    case GST_PROPS_INT_RANGE_TYPE:						\
      entry->data.int_range_data.min = va_arg (var_args, gint);			\
      entry->data.int_range_data.max = va_arg (var_args, gint);			\
      break;									\
    case GST_PROPS_FLOAT_TYPE:							\
      entry->data.float_data = va_arg (var_args, gdouble);			\
      break;									\
    case GST_PROPS_FLOAT_RANGE_TYPE:						\
      entry->data.float_range_data.min = va_arg (var_args, gdouble);		\
      entry->data.float_range_data.max = va_arg (var_args, gdouble);		\
      break;									\
    case GST_PROPS_FOURCC_TYPE:							\
      entry->data.fourcc_data = va_arg (var_args, gulong);			\
      break;									\
    case GST_PROPS_BOOLEAN_TYPE:						\
      entry->data.bool_data = va_arg (var_args, gboolean);			\
      break;									\
    case GST_PROPS_STRING_TYPE:							\
      entry->data.string_data.string = g_strdup (va_arg (var_args, gchar*));	\
      break;									\
    case GST_PROPS_GLIST_TYPE:							\
      entry->propstype = GST_PROPS_LIST_TYPE;					\
      entry->data.list_data.entries = g_list_copy (va_arg (var_args, GList*));	\
      break;									\
    default:									\
      break;									\
  }										\
} G_STMT_END


#define GST_PROPS_ENTRY_READ(entry, var_args, safe, result)			\
G_STMT_START { 									\
										\
  *result = TRUE;								\
										\
  if (safe) {									\
    GstPropsType propstype = va_arg (var_args, GstPropsType); 			\
    if (propstype != entry->propstype) {					\
      *result = FALSE;								\
    }										\
  }										\
  if (*result) {								\
    switch (entry->propstype) {							\
      case GST_PROPS_INT_TYPE:							\
        *(va_arg (var_args, gint*)) = entry->data.int_data;			\
        break;									\
      case GST_PROPS_INT_RANGE_TYPE:						\
        *(va_arg (var_args, gint*)) = entry->data.int_range_data.min;		\
        *(va_arg (var_args, gint*)) = entry->data.int_range_data.max;		\
        break;									\
      case GST_PROPS_FLOAT_TYPE:						\
        *(va_arg (var_args, gfloat*)) = entry->data.float_data;			\
        break;									\
      case GST_PROPS_FLOAT_RANGE_TYPE:						\
        *(va_arg (var_args, gfloat*)) = entry->data.float_range_data.min;	\
        *(va_arg (var_args, gfloat*)) = entry->data.float_range_data.max;	\
        break;									\
      case GST_PROPS_FOURCC_TYPE:						\
        *(va_arg (var_args, guint32*)) = entry->data.fourcc_data;		\
        break;									\
      case GST_PROPS_BOOLEAN_TYPE:						\
        *(va_arg (var_args, gboolean*)) = entry->data.bool_data;		\
        break;									\
      case GST_PROPS_STRING_TYPE:						\
        *(va_arg (var_args, gchar**)) = entry->data.string_data.string;		\
        break;									\
      case GST_PROPS_LIST_TYPE:							\
        *(va_arg (var_args, GList**)) = entry->data.list_data.entries;		\
        break;									\
      default:									\
        *result = FALSE;							\
        break;									\
    }										\
  }										\
} G_STMT_END

static GstPropsEntry*
gst_props_alloc_entry (void)
{
  GstPropsEntry *entry;

  entry = gst_mem_chunk_alloc (_gst_props_entries_chunk);

  GST_DEBUG (GST_CAT_PROPERTIES, "new entry %p", entry);

  return entry;
}

static void
gst_props_entry_clean (GstPropsEntry *entry)
{
  switch (entry->propstype) {
    case GST_PROPS_STRING_TYPE:
      g_free (entry->data.string_data.string);
      break;
    case GST_PROPS_LIST_TYPE:
      g_list_foreach (entry->data.list_data.entries, (GFunc) gst_props_entry_destroy, NULL);
      g_list_free (entry->data.list_data.entries);
      break;
    default:
      break;
  }
}

/**
 * gst_props_entry_destroy:
 * @entry: the entry to destroy
 *
 * Free the given propsentry
 */
void
gst_props_entry_destroy (GstPropsEntry *entry)
{
  GST_DEBUG (GST_CAT_PROPERTIES, "destroy entry %p", entry);

  gst_props_entry_clean (entry);

  gst_mem_chunk_free (_gst_props_entries_chunk, entry);
}

GType
gst_props_get_type (void)
{
  return _gst_props_type;
}

/**
 * gst_props_empty_new:
 *
 * Create a new empty property.
 *
 * Returns: the new property
 */
GstProps*
gst_props_empty_new (void)
{
  GstProps *props;

  props = gst_mem_chunk_alloc (_gst_props_chunk);

  GST_DEBUG (GST_CAT_PROPERTIES, "new %p", props);

  props->properties = NULL;
  props->refcount = 1;
  GST_PROPS_FLAG_SET (props, GST_PROPS_FLOATING);
  GST_PROPS_FLAG_SET (props, GST_PROPS_FIXED);

  return props;
}

/**
 * gst_props_replace:
 * @oldprops: the props to take replace
 * @newprops: the props to take replace 
 *
 * Replace the pointer to the props, doing proper
 * refcounting.
 */
void
gst_props_replace (GstProps **oldprops, GstProps *newprops)
{
  if (*oldprops != newprops) {
    if (newprops)  gst_props_ref   (newprops);
    if (*oldprops) gst_props_unref (*oldprops);

    *oldprops = newprops;
  }
}

/**
 * gst_props_replace_sink:
 * @oldprops: the props to take replace
 * @newprops: the props to take replace 
 *
 * Replace the pointer to the props and take ownership.
 */
void
gst_props_replace_sink (GstProps **oldprops, GstProps *newprops)
{
  gst_props_replace (oldprops, newprops);
  gst_props_sink (newprops);
}

/**
 * gst_props_add_entry:
 * @props: the property to add the entry to
 * @entry: the entry to add
 *
 * Addes the given propsentry to the props
 */
void
gst_props_add_entry (GstProps *props, GstPropsEntry *entry)
{
  g_return_if_fail (props);
  g_return_if_fail (entry);

  /* only variable properties can change the fixed flag */
  if (GST_PROPS_IS_FIXED (props) && GST_PROPS_ENTRY_IS_VARIABLE (entry)) {
    GST_PROPS_FLAG_UNSET (props, GST_PROPS_FIXED);
  }
  props->properties = g_list_insert_sorted (props->properties, entry, props_compare_func);
}

static void
gst_props_remove_entry_by_id (GstProps *props, GQuark propid)
{
  GList *properties;
  gboolean found;
  
  /* assume fixed */
  GST_PROPS_FLAG_SET (props, GST_PROPS_FIXED);

  found = FALSE;

  properties = props->properties;
  while (properties) {
    GList *current = properties;
    GstPropsEntry *lentry = (GstPropsEntry *) current->data;
    
    properties = g_list_next (properties);

    if (lentry->propid == propid) {
      found = TRUE;
      g_list_delete_link (props->properties, current);
    }
    else if (GST_PROPS_ENTRY_IS_VARIABLE (lentry)) {
      GST_PROPS_FLAG_UNSET (props, GST_PROPS_FIXED);
      /* no need to check for further variable entries
       * if we already removed the entry */
      if (found)
	break;
    }
  }
}
/**
 * gst_props_remove_entry:
 * @props: the property to remove the entry from
 * @entry: the entry to remove
 *
 * Removes the given propsentry from the props.
 */
void
gst_props_remove_entry (GstProps *props, GstPropsEntry *entry)
{
  g_return_if_fail (props != NULL);
  g_return_if_fail (entry != NULL);

  gst_props_remove_entry_by_id (props, entry->propid);
}

/**
 * gst_props_remove_entry_by_name:
 * @props: the property to remove the entry from
 * @name: the name of the entry to remove
 *
 * Removes the propsentry with the given name from the props.
 */
void
gst_props_remove_entry_by_name (GstProps *props, const gchar *name)
{
  GQuark quark;

  g_return_if_fail (props != NULL);
  g_return_if_fail (name != NULL);

  quark = g_quark_from_string (name);
  gst_props_remove_entry_by_id (props, quark);
}

/**
 * gst_props_new:
 * @firstname: the first property name
 * @...: the property values
 *
 * Create a new property from the given key/value pairs
 *
 * Returns: the new property
 */
GstProps*
gst_props_new (const gchar *firstname, ...)
{
  GstProps *props;
  va_list var_args;

  va_start (var_args, firstname);

  props = gst_props_newv (firstname, var_args);

  va_end (var_args);

  return props;
}

/**
 * gst_props_debug:
 * @props: the props to debug
 *
 * Dump the contents of the given properties into the DEBUG log.
 */
void
gst_props_debug (GstProps *props)
{
  if (!props) {
    GST_DEBUG (GST_CAT_PROPERTIES, "props (null)");
    return;
  }
	  
  GST_DEBUG (GST_CAT_PROPERTIES, "props %p, refcount %d, flags %d", 
		  props, props->refcount, props->flags);

  g_list_foreach (props->properties, (GFunc) gst_props_debug_entry, NULL);
}

/**
 * gst_props_merge_int_entries:
 * @newentry: the new entry
 * @oldentry: an old entry
 *
 * Tries to merge oldentry into newentry, if there is a simpler single entry which represents
 *
 * Assumes that the entries are either ints or int ranges.
 *
 * Returns: TRUE if the entries were merged, FALSE otherwise.
 */
static gboolean
gst_props_merge_int_entries(GstPropsEntry *newentry, GstPropsEntry *oldentry)
{
  gint new_min, new_max, old_min, old_max;
  gboolean can_merge = FALSE;

  if (newentry->propstype == GST_PROPS_INT_TYPE) {
    new_min = newentry->data.int_data;
    new_max = newentry->data.int_data;
  } else {
    new_min = newentry->data.int_range_data.min;
    new_max = newentry->data.int_range_data.max;
  }

  if (oldentry->propstype == GST_PROPS_INT_TYPE) {
    old_min = oldentry->data.int_data;
    old_max = oldentry->data.int_data;
  } else {
    old_min = oldentry->data.int_range_data.min;
    old_max = oldentry->data.int_range_data.max;
  }

  /* Put range which starts lower into (new_min, new_max) */
  if (old_min < new_min) {
    gint tmp;
    tmp = old_min;
    old_min = new_min;
    new_min = tmp;
    tmp = old_max;
    old_max = new_max;
    new_max = tmp;
  }

  /* new_min is min of either entry - second half of the following conditional */
  /* is to avoid overflow problems. */
  if (new_max >= old_min - 1 && old_min - 1 < old_min) {
    /* ranges overlap, or are adjacent.  Pick biggest maximum. */
    can_merge = TRUE;
    if (old_max > new_max) new_max = old_max;
  }

  if (can_merge) {
    if (new_min == new_max) {
      newentry->propstype = GST_PROPS_INT_TYPE;
      newentry->data.int_data = new_min;
    } else {
      newentry->propstype = GST_PROPS_INT_RANGE_TYPE;
      newentry->data.int_range_data.min = new_min;
      newentry->data.int_range_data.max = new_max;
    }
  }
  return can_merge;
}

/**
 * gst_props_add_to_int_list:
 * @entries: the existing list of entries
 * @entry: the new entry to add to the list
 *
 * Add an integer property to a list of properties, removing duplicates
 * and merging ranges.
 *
 * Assumes that the existing list is in simplest form, contains
 * only ints and int ranges, and that the new entry is an int or 
 * an int range.
 *
 * Returns: a pointer to a list with the new entry added.
 */
static GList*
gst_props_add_to_int_list (GList *entries, GstPropsEntry *newentry)
{
  GList *i;

  i = entries;
  while (i) {
    GstPropsEntry *oldentry = (GstPropsEntry *)(i->data);
    gboolean merged = gst_props_merge_int_entries(newentry, oldentry);

    if (merged) {
      /* replace the existing one with the merged one */
      gst_props_entry_destroy (oldentry);

      entries = g_list_remove_link (entries, i);
      g_list_free_1 (i);

      /* start again: it's possible that this change made an earlier entry */
      /* mergeable, and the pointer is now invalid anyway. */
      i = entries;
    }

    i = g_list_next (i);
  }

  return g_list_prepend (entries, newentry);
}

static GstPropsEntry*
gst_props_entry_newv (const gchar *name, va_list var_args)
{
  GstPropsEntry *entry;

  entry = gst_props_alloc_entry ();
  entry->propid = g_quark_from_string (name);
  GST_PROPS_ENTRY_FILL (entry, var_args);

  return entry;
}

/**
 * gst_props_entry_new:
 * @name: the name of the props entry
 * @...: the value of the entry
 *
 * Create a new property entry with the given key/value.
 *
 * Returns: the new entry.
 */
GstPropsEntry*
gst_props_entry_new (const gchar *name, ...)
{
  va_list var_args;
  GstPropsEntry *entry;

  va_start (var_args, name);
  entry = gst_props_entry_newv (name, var_args);
  va_end (var_args);

  return entry;
}

/**
 * gst_props_newv:
 * @firstname: the first property name
 * @var_args: the property values
 *
 * Create a new property from the list of entries.
 *
 * Returns: the new property created from the list of entries
 */
GstProps*
gst_props_newv (const gchar *firstname, va_list var_args)
{
  GstProps *props;
  gboolean inlist = FALSE;
  const gchar *prop_name;
  GstPropsEntry *list_entry = NULL;

  typedef enum {
      GST_PROPS_LIST_T_UNSET,
      GST_PROPS_LIST_T_INTS,
      GST_PROPS_LIST_T_FLOATS,
      GST_PROPS_LIST_T_MISC,
  } list_types;

  /* type of the list */
  list_types list_type = GST_PROPS_LIST_T_UNSET;
  /* type of current item */
  list_types entry_type = GST_PROPS_LIST_T_UNSET;

  if (firstname == NULL)
    return NULL;

  props = gst_props_empty_new ();

  prop_name = firstname;

  /* properties */
  while (prop_name) {
    GstPropsEntry *entry;

    entry = gst_props_alloc_entry ();
    entry->propid = g_quark_from_string (prop_name);
    GST_PROPS_ENTRY_FILL (entry, var_args);

    switch (entry->propstype) {
      case GST_PROPS_INT_TYPE:
      case GST_PROPS_INT_RANGE_TYPE:
	entry_type = GST_PROPS_LIST_T_INTS;
	break;
      case GST_PROPS_FLOAT_TYPE:
      case GST_PROPS_FLOAT_RANGE_TYPE:
	entry_type = GST_PROPS_LIST_T_FLOATS;
	break;
      case GST_PROPS_FOURCC_TYPE:
      case GST_PROPS_BOOLEAN_TYPE:
      case GST_PROPS_STRING_TYPE:
	entry_type = GST_PROPS_LIST_T_MISC;
	break;
      case GST_PROPS_LIST_TYPE:
	g_return_val_if_fail (inlist == FALSE, NULL);
	inlist = TRUE;
	list_entry = entry;
	list_type = GST_PROPS_LIST_T_UNSET;
	list_entry->data.list_data.entries = NULL;
	break;
      case GST_PROPS_END_TYPE:
	g_return_val_if_fail (inlist == TRUE, NULL);

	/* if list was of size 1, replace the list by a the item it contains */
	if (g_list_length(list_entry->data.list_data.entries) == 1) {
	  GstPropsEntry *subentry = (GstPropsEntry *)(list_entry->data.list_data.entries->data);
	  list_entry->propstype = subentry->propstype;
	  list_entry->data = subentry->data;
          gst_props_entry_destroy (subentry);
	}
	else {
	  list_entry->data.list_data.entries =
		    g_list_reverse (list_entry->data.list_data.entries);
	}

        gst_props_entry_destroy (entry);
	inlist = FALSE;
	list_entry = NULL;
        prop_name = va_arg (var_args, gchar*);
	continue;
      default:
	g_warning ("unknown property type found %d for '%s'\n", entry->propstype, prop_name);
        gst_props_entry_destroy (entry);
	break;
    }

    if (inlist && (list_entry != entry)) {
      if (list_type == GST_PROPS_LIST_T_UNSET) list_type = entry_type;
      if (list_type != entry_type) {
	g_warning ("property list contained incompatible entry types\n");
      } else {
	switch (list_type) {
	  case GST_PROPS_LIST_T_INTS:
	    list_entry->data.list_data.entries =
		    gst_props_add_to_int_list (list_entry->data.list_data.entries, entry);
	    break;
	  default:
	    list_entry->data.list_data.entries =
		    g_list_prepend (list_entry->data.list_data.entries, entry);
	    break;
	}
      }
    }
    else {
      gst_props_add_entry (props, entry);
    }
    if (!inlist)
      prop_name = va_arg (var_args, gchar*);
  }

  return props;
}

/**
 * gst_props_set:
 * @props: the props to modify
 * @name: the name of the entry to modify
 * @...: The prop entry.
 *
 * Modifies the value of the given entry in the props struct.
 * For the optional args, use GST_PROPS_FOO, where FOO is INT,
 * STRING, etc. This macro expands to a variable number of arguments,
 * hence the lack of precision in the function prototype. No
 * terminating NULL is necessary as only one property can be changed.
 *
 * Returns: the new modified property structure.
 */
GstProps*
gst_props_set (GstProps *props, const gchar *name, ...)
{
  GQuark quark;
  GList *properties;
  va_list var_args;
  gboolean found;
  gboolean was_fixed;

  g_return_val_if_fail (props != NULL, NULL);

  found = FALSE;
  was_fixed = GST_PROPS_IS_FIXED (props);
  GST_PROPS_FLAG_SET (props, GST_PROPS_FIXED);

  quark = g_quark_from_string (name);
  /* this looks a little complicated but the idea is to get
   * out of the loop ASAP. Changing the entry to a variable
   * property immediatly marks the props as non-fixed.
   * changing the entry to fixed when the props was fixed 
   * does not change the props and we can get out of the loop
   * as well.
   * When changing the entry to a fixed entry, we need to 
   * see if all entries are fixed before we can decide the props
   */
  properties = props->properties;
  while (properties) {
    GstPropsEntry *entry;

    entry = (GstPropsEntry *) properties->data;

    if (entry->propid == quark) {
      found = TRUE;

      va_start (var_args, name);
      gst_props_entry_clean (entry);
      GST_PROPS_ENTRY_FILL (entry, var_args);
      va_end (var_args);

      /* if the props was fixed and we changed this entry
       * with a fixed entry, we can stop now as the global
       * props flag cannot change */
      if (was_fixed && !GST_PROPS_ENTRY_IS_VARIABLE (entry))
	break;
      /* if we already found a non fixed entry we can exit */
      if (!GST_PROPS_IS_FIXED (props))
	break;
      /* if the entry is variable, we'll get out of the loop
       * in the next statement */
      /* if the entry is fixed we have to check all other
       * entries before we can decide if the props are fixed */
    }

    if (GST_PROPS_ENTRY_IS_VARIABLE (entry)) {
      /* mark the props as variable */
      GST_PROPS_FLAG_UNSET (props, GST_PROPS_FIXED);
      /* if we already changed the entry, we can stop now */
      if (found)
        break;
    }
    properties = g_list_next (properties);
  }

  if (!found) {
    g_warning ("gstprops: no property '%s' to change\n", name);
  }

  return props;
}


/**
 * gst_props_unref:
 * @props: the props to unref
 *
 * Decrease the refcount of the property structure, destroying
 * the property if the refcount is 0.
 *
 * Returns: handle to unrefed props or NULL when it was
 * destroyed.
 */
GstProps*
gst_props_unref (GstProps *props)
{
  if (props == NULL)
    return NULL;

  g_return_val_if_fail (props->refcount > 0, NULL);

  GST_DEBUG (GST_CAT_PROPERTIES, "unref %p (%d->%d)", props, props->refcount, props->refcount-1);
  props->refcount--;

  if (props->refcount == 0) {
    gst_props_destroy (props);
    return NULL;
  }

  return props;
}

/**
 * gst_props_ref:
 * @props: the props to ref
 *
 * Increase the refcount of the property structure.
 *
 * Returns: handle to refed props.
 */
GstProps*
gst_props_ref (GstProps *props)
{
  if (props == NULL)
    return NULL;

  g_return_val_if_fail (props->refcount > 0, NULL);

  GST_DEBUG (GST_CAT_PROPERTIES, "ref %p (%d->%d)", props, props->refcount, props->refcount+1);

  props->refcount++;

  return props;
}

/**
 * gst_props_sink:
 * @props: the props to sink
 *
 * If the props if floating, decrease its refcount. Usually used 
 * with gst_props_ref() to take ownership of the props.
 */
void
gst_props_sink (GstProps *props)
{
  if (props == NULL)
    return;

  GST_DEBUG (GST_CAT_PROPERTIES, "sink %p", props);

  if (GST_PROPS_IS_FLOATING (props)) {
    GST_PROPS_FLAG_UNSET (props, GST_PROPS_FLOATING);
    gst_props_unref (props);
  }
}

/**
 * gst_props_destroy:
 * @props: the props to destroy
 *
 * Destroy the property, freeing all the memory that
 * was allocated.
 */
void
gst_props_destroy (GstProps *props)
{
  if (props == NULL)
    return;

  g_list_foreach (props->properties, (GFunc) gst_props_entry_destroy, NULL);
  g_list_free (props->properties);

  gst_mem_chunk_free (_gst_props_chunk, props);
}

/**
 * gst_props_entry_copy:
 * @entry: the entry to copy
 *
 * Copy the propsentry.
 *
 * Returns: a new #GstPropsEntry that is a copy of the original
 * given entry.
 */
GstPropsEntry*
gst_props_entry_copy (GstPropsEntry *entry)
{
  GstPropsEntry *newentry;

  newentry = gst_props_alloc_entry ();
  memcpy (newentry, entry, sizeof (GstPropsEntry));

  switch (entry->propstype) {
    case GST_PROPS_LIST_TYPE:
      newentry->data.list_data.entries = gst_props_list_copy (entry->data.list_data.entries);
      break;
    case GST_PROPS_STRING_TYPE:
      newentry->data.string_data.string = g_strdup (entry->data.string_data.string);
      break;
    default:
      /* FIXME more? */
      break;
  }

  return newentry;
}

static GList*
gst_props_list_copy (GList *propslist)
{
  GList *new = NULL;

  while (propslist) {
    GstPropsEntry *entry = (GstPropsEntry *)propslist->data;

    new = g_list_prepend (new, gst_props_entry_copy (entry));

    propslist = g_list_next (propslist);
  }
  new = g_list_reverse (new);

  return new;
}

/**
 * gst_props_copy:
 * @props: the props to copy
 *
 * Copy the property structure.
 *
 * Returns: the new property that is a copy of the original
 * one.
 */
GstProps*
gst_props_copy (GstProps *props)
{
  GstProps *new;

  if (props == NULL)
    return NULL;

  new = gst_props_empty_new ();
  new->properties = gst_props_list_copy (props->properties);
  GST_PROPS_FLAGS (new) = GST_PROPS_FLAGS (props) | GST_PROPS_FLOATING;

  return new;
}

/**
 * gst_props_copy_on_write:
 * @props: the props to copy on write
 *
 * Copy the property structure if the refcount is >1.
 *
 * Returns: A new props that can be safely written to.
 */
GstProps*
gst_props_copy_on_write (GstProps *props)
{
  GstProps *new = props;;

  g_return_val_if_fail (props != NULL, NULL);

  if (props->refcount > 1) {
    new = gst_props_copy (props);
    gst_props_unref (props);
  }

  return new;
}

/**
 * gst_props_get_entry:
 * @props: the props to query
 * @name: the name of the entry to get
 *
 * Get the props entry with the geven name
 *
 * Returns: The props entry with the geven name or NULL when
 * the entry was not found.
 */
const GstPropsEntry*
gst_props_get_entry (GstProps *props, const gchar *name)
{
  GList *lentry;
  GQuark quark;

  g_return_val_if_fail (props != NULL, NULL);
  g_return_val_if_fail (name != NULL, NULL);

  quark = g_quark_from_string (name);

  lentry = g_list_find_custom (props->properties, GINT_TO_POINTER (quark), props_find_func);

  if (lentry) {
    GstPropsEntry *thisentry;
    thisentry = (GstPropsEntry *)lentry->data;
    return thisentry;
  }
  return NULL;
}

/**
 * gst_props_has_property:
 * @props: the props to check
 * @name: the name of the key to find
 *
 * Checks if a given props has a property with the given name.
 *
 * Returns: TRUE if the property was found, FALSE otherwise.
 */
gboolean
gst_props_has_property (GstProps *props, const gchar *name)
{
  return (gst_props_get_entry (props, name) != NULL);
}

/**
 * gst_props_has_property_typed:
 * @props: the props to check
 * @name: the name of the key to find
 * @type: the type of the required property
 *
 * Checks if a given props has a property with the given name and the given type.
 *
 * Returns: TRUE if the property was found, FALSE otherwise.
 */
gboolean
gst_props_has_property_typed (GstProps *props, const gchar *name, GstPropsType type)
{
  const GstPropsEntry *entry;

  entry = gst_props_get_entry (props, name);
  if (!entry) 
    return FALSE;

  return (entry->propstype == type);
}

/**
 * gst_props_has_fixed_property:
 * @props: the props to check
 * @name: the name of the key to find
 *
 * Checks if a given props has a property with the given name that
 * is also fixed, ie. is not a list or a range.
 *
 * Returns: TRUE if the property was found, FALSE otherwise.
 */
gboolean
gst_props_has_fixed_property (GstProps *props, const gchar *name)
{
  const GstPropsEntry *entry;

  entry = gst_props_get_entry (props, name);
  if (!entry)
    return FALSE;

  return !GST_PROPS_ENTRY_IS_VARIABLE (entry);
}

/**
 * gst_props_entry_get_props_type:
 * @entry: the props entry to query
 *
 * Get the type of the given props entry.
 *
 * Returns: The type of the props entry.
 */
GstPropsType
gst_props_entry_get_type (const GstPropsEntry *entry)
{
  g_return_val_if_fail (entry != NULL, GST_PROPS_INVALID_TYPE);

  return entry->propstype;
}

/**
 * gst_props_entry_get_name:
 * @entry: the props entry to query
 *
 * Get the name of the given props entry. 
 *
 * Returns: The name of the props entry.
 */
const gchar*
gst_props_entry_get_name (const GstPropsEntry *entry)
{
  g_return_val_if_fail (entry != NULL, NULL);

  return g_quark_to_string (entry->propid);
}

/**
 * gst_props_entry_is_fixed:
 * @entry: the props entry to query
 *
 * Checks if the props entry is fixe, ie. is not a list
 * or a range.
 *
 * Returns: TRUE is the props entry is fixed.
 */
gboolean
gst_props_entry_is_fixed (const GstPropsEntry *entry)
{
  g_return_val_if_fail (entry != NULL, FALSE);

  return !GST_PROPS_ENTRY_IS_VARIABLE (entry);
}

static gboolean
gst_props_entry_getv (const GstPropsEntry *entry, gboolean safe, va_list var_args)
{
  gboolean result;

  GST_PROPS_ENTRY_READ (entry, var_args, safe, &result);

  return result;
}

/**
 * gst_props_entry_get:
 * @entry: the props entry to query
 * @...: a pointer to a type that can hold the value.
 *
 * Gets the contents of the entry.
 *
 * Returns: TRUE is the props entry could be fetched.
 */
gboolean
gst_props_entry_get (const GstPropsEntry *entry, ...)
{
  gboolean result;
  va_list var_args;

  g_return_val_if_fail (entry != NULL, FALSE);

  va_start (var_args, entry);
  result = gst_props_entry_getv (entry, FALSE, var_args);
  va_end (var_args);

  return result;
}

static gboolean
gst_props_entry_get_safe (const GstPropsEntry *entry, ...)
{
  gboolean result;
  va_list var_args;

  g_return_val_if_fail (entry != NULL, FALSE);

  va_start (var_args, entry);
  result = gst_props_entry_getv (entry, TRUE, var_args);
  va_end (var_args);

  return result;
}

static gboolean
gst_props_getv (GstProps *props, gboolean safe, gchar *first_name, va_list var_args)
{
  while (first_name) {
    const GstPropsEntry *entry = gst_props_get_entry (props, first_name);
    gboolean result;

    if (!entry) return FALSE;
    GST_PROPS_ENTRY_READ (entry, var_args, safe, &result);
    if (!result) return FALSE;

    first_name = va_arg (var_args, gchar *);
  }
  return TRUE;
}

/**
 * gst_props_get:
 * @props: the props to query
 * @first_name: the first key
 * @...: a pointer to a datastructure that can hold the value.
 *
 * Gets the contents of the props into given key/value pairs.
 * Make sure you pass a NULL terminated list.
 *
 * Returns: TRUE if all of the props entries could be fetched.
 */
gboolean
gst_props_get (GstProps *props, gchar *first_name, ...)
{
  va_list var_args;
  gboolean ret;

  va_start (var_args, first_name);
  ret = gst_props_getv (props, FALSE, first_name, var_args);
  va_end (var_args);

  return ret;
}

/**
 * gst_props_get_safe:
 * @props: the props to query
 * @first_name: the first key
 * @...: a pointer to a datastructure that can hold the value.
 *
 * Gets the contents of the props into given key/value pairs.
 *
 * Returns: TRUE if all of the props entries could be fetched.
 */
gboolean
gst_props_get_safe (GstProps *props, gchar *first_name, ...)
{
  va_list var_args;
  gboolean ret;

  va_start (var_args, first_name);
  ret = gst_props_getv (props, TRUE, first_name, var_args);
  va_end (var_args);

  return ret;
}

/**
 * gst_props_entry_get_int:
 * @entry: the props entry to query
 * @val: a pointer to a gint to hold the value.
 *
 * Get the contents of the entry into the given gint.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_int (const GstPropsEntry *entry, gint *val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_INT_TYPE, val);
}

/**
 * gst_props_entry_get_float:
 * @entry: the props entry to query
 * @val: a pointer to a gfloat to hold the value.
 *
 * Get the contents of the entry into the given gfloat.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_float (const GstPropsEntry *entry, gfloat *val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_FLOAT_TYPE, val);
}

/**
 * gst_props_entry_get_fourcc_int:
 * @entry: the props entry to query
 * @val: a pointer to a guint32 to hold the value.
 *
 * Get the contents of the entry into the given guint32.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_fourcc_int (const GstPropsEntry *entry, guint32 *val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_FOURCC_TYPE, val);
}

/**
 * gst_props_entry_get_boolean:
 * @entry: the props entry to query
 * @val: a pointer to a gboolean to hold the value.
 *
 * Get the contents of the entry into the given gboolean.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_boolean (const GstPropsEntry *entry, gboolean *val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_BOOLEAN_TYPE, val);
}

/**
 * gst_props_entry_get_string:
 * @entry: the props entry to query
 * @val: a pointer to a gchar* to hold the value.
 *
 * Get the contents of the entry into the given gchar*.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_string (const GstPropsEntry *entry, const gchar **val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_STRING_TYPE, val);
}

/**
 * gst_props_entry_get_int_range:
 * @entry: the props entry to query
 * @min: a pointer to a gint to hold the minimun value.
 * @max: a pointer to a gint to hold the maximum value.
 *
 * Get the contents of the entry into the given gints.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_int_range (const GstPropsEntry *entry, gint *min, gint *max)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_INT_RANGE_TYPE, min, max);
}

/**
 * gst_props_entry_get_float_range:
 * @entry: the props entry to query
 * @min: a pointer to a gfloat to hold the minimun value.
 * @max: a pointer to a gfloat to hold the maximum value.
 *
 * Get the contents of the entry into the given gfloats.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_float_range (const GstPropsEntry *entry, gfloat *min, gfloat *max)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_FLOAT_RANGE_TYPE, min, max);
}

/**
 * gst_props_entry_get_list:
 * @entry: the props entry to query
 * @val: a pointer to a GList to hold the value.
 *
 * Get the contents of the entry into the given GList.
 *
 * Returns: TRUE is the value could be fetched. FALSE if the 
 * entry is not of given type or did not exist.
 */
gboolean
gst_props_entry_get_list (const GstPropsEntry *entry, const GList **val)
{
  return gst_props_entry_get_safe (entry, GST_PROPS_LIST_TYPE, val);
}

/**
 * gst_props_merge:
 * @props: the property to merge into
 * @tomerge: the property to merge 
 *
 * Merge the properties of tomerge into props.
 *
 * Returns: the new merged property 
 */
GstProps*
gst_props_merge (GstProps *props, GstProps *tomerge)
{
  GList *merge_props;

  g_return_val_if_fail (props != NULL, NULL);
  g_return_val_if_fail (tomerge != NULL, NULL);

  merge_props = tomerge->properties;

  /* FIXME do proper merging here... */
  while (merge_props) {
    GstPropsEntry *entry = (GstPropsEntry *)merge_props->data;

    gst_props_add_entry (props, entry);

    merge_props = g_list_next (merge_props);
  }

  return props;
}


/* entry2 is always a list, entry1 never is */
static gboolean
gst_props_entry_check_list_compatibility (GstPropsEntry *entry1, GstPropsEntry *entry2)
{
  GList *entrylist = entry2->data.list_data.entries;
  gboolean found = FALSE;

  while (entrylist && !found) {
    GstPropsEntry *entry = (GstPropsEntry *) entrylist->data;

    found |= gst_props_entry_check_compatibility (entry1, entry);

    entrylist = g_list_next (entrylist);
  }

  return found;
}

static gboolean
gst_props_entry_check_compatibility (GstPropsEntry *entry1, GstPropsEntry *entry2)
{
  GST_DEBUG (GST_CAT_PROPERTIES,"compare: %s %s", g_quark_to_string (entry1->propid), g_quark_to_string (entry2->propid));

  if (entry2->propstype == GST_PROPS_LIST_TYPE && entry1->propstype != GST_PROPS_LIST_TYPE) {
    return gst_props_entry_check_list_compatibility (entry1, entry2);
  }

  switch (entry1->propstype) {
    case GST_PROPS_LIST_TYPE:
    {
      GList *entrylist = entry1->data.list_data.entries;
      gboolean valid = TRUE;    /* innocent until proven guilty */

      while (entrylist && valid) {
	GstPropsEntry *entry = (GstPropsEntry *) entrylist->data;

	valid &= gst_props_entry_check_compatibility (entry, entry2);
	
	entrylist = g_list_next (entrylist);
      }
      
      return valid;
    }
    case GST_PROPS_INT_RANGE_TYPE:
      switch (entry2->propstype) {
	/* a - b   <--->   a - c */
        case GST_PROPS_INT_RANGE_TYPE:
	  return (entry2->data.int_range_data.min <= entry1->data.int_range_data.min &&
	          entry2->data.int_range_data.max >= entry1->data.int_range_data.max);
        default:
	  break;
      }
      break;
    case GST_PROPS_FLOAT_RANGE_TYPE:
      switch (entry2->propstype) {
	/* a - b   <--->   a - c */
        case GST_PROPS_FLOAT_RANGE_TYPE:
	  return (entry2->data.float_range_data.min <= entry1->data.float_range_data.min &&
	          entry2->data.float_range_data.max >= entry1->data.float_range_data.max);
        default:
	  break;
      }
      break;
    case GST_PROPS_FOURCC_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a */
        case GST_PROPS_FOURCC_TYPE:
          GST_DEBUG(GST_CAT_PROPERTIES,"\"%c%c%c%c\" <--> \"%c%c%c%c\" ?",
	    (entry2->data.fourcc_data>>0)&0xff,
	    (entry2->data.fourcc_data>>8)&0xff,
	    (entry2->data.fourcc_data>>16)&0xff,
	    (entry2->data.fourcc_data>>24)&0xff,
	    (entry1->data.fourcc_data>>0)&0xff,
	    (entry1->data.fourcc_data>>8)&0xff,
	    (entry1->data.fourcc_data>>16)&0xff,
	    (entry1->data.fourcc_data>>24)&0xff);
	  return (entry2->data.fourcc_data == entry1->data.fourcc_data);
        default:
	  break;
      }
      break;
    case GST_PROPS_INT_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a - d */
        case GST_PROPS_INT_RANGE_TYPE:
          GST_DEBUG(GST_CAT_PROPERTIES,"%d <= %d <= %d ?",entry2->data.int_range_data.min,
                    entry1->data.int_data,entry2->data.int_range_data.max);
	  return (entry2->data.int_range_data.min <= entry1->data.int_data &&
	          entry2->data.int_range_data.max >= entry1->data.int_data);
	/* b   <--->   a */
        case GST_PROPS_INT_TYPE:
          GST_DEBUG(GST_CAT_PROPERTIES,"%d == %d ?",entry1->data.int_data,entry2->data.int_data);
	  return (entry2->data.int_data == entry1->data.int_data);
        default:
	  break;
      }
      break;
    case GST_PROPS_FLOAT_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a - d */
        case GST_PROPS_FLOAT_RANGE_TYPE:
	  return (entry2->data.float_range_data.min <= entry1->data.float_data &&
	          entry2->data.float_range_data.max >= entry1->data.float_data);
	/* b   <--->   a */
        case GST_PROPS_FLOAT_TYPE:
	  return (entry2->data.float_data == entry1->data.float_data);
        default:
	  break;
      }
      break;
    case GST_PROPS_BOOLEAN_TYPE:
      switch (entry2->propstype) {
	/* t   <--->   t */
        case GST_PROPS_BOOLEAN_TYPE:
          return (entry2->data.bool_data == entry1->data.bool_data);
        default:
	  break;
      }
    case GST_PROPS_STRING_TYPE:
      switch (entry2->propstype) {
	/* t   <--->   t */
        case GST_PROPS_STRING_TYPE:
          GST_DEBUG(GST_CAT_PROPERTIES,"\"%s\" <--> \"%s\" ?",
			  entry2->data.string_data.string, entry1->data.string_data.string);
          return (!strcmp (entry2->data.string_data.string, entry1->data.string_data.string));
        default:
	  break;
      }
    default:
      break;
  }

  return FALSE;
}

/**
 * gst_props_check_compatibility:
 * @fromprops: a property
 * @toprops: a property
 *
 * Checks whether two capabilities are compatible.
 *
 * Returns: TRUE if compatible, FALSE otherwise
 */
gboolean
gst_props_check_compatibility (GstProps *fromprops, GstProps *toprops)
{
  GList *sourcelist;
  GList *sinklist;
  gint missing = 0;
  gint more = 0;
  gboolean compatible = TRUE;

  g_return_val_if_fail (fromprops != NULL, FALSE);
  g_return_val_if_fail (toprops != NULL, FALSE);

  sourcelist = fromprops->properties;
  sinklist   = toprops->properties;

  while (sourcelist && sinklist && compatible) {
    GstPropsEntry *entry1;
    GstPropsEntry *entry2;

    entry1 = (GstPropsEntry *)sourcelist->data;
    entry2 = (GstPropsEntry *)sinklist->data;

    while (entry1->propid < entry2->propid) {
      more++;
      sourcelist = g_list_next (sourcelist);
      if (sourcelist) entry1 = (GstPropsEntry *)sourcelist->data;
      else goto end;
    }
    while (entry1->propid > entry2->propid) {
      missing++;
      sinklist = g_list_next (sinklist);
      if (sinklist) entry2 = (GstPropsEntry *)sinklist->data;
      else goto end;
    }

    if (!gst_props_entry_check_compatibility (entry1, entry2)) {
	compatible = FALSE; 
	GST_DEBUG (GST_CAT_PROPERTIES, "%s are not compatible: ",
		   g_quark_to_string (entry1->propid));
    }

    sourcelist = g_list_next (sourcelist);
    sinklist = g_list_next (sinklist);
  }
  if (sinklist && compatible) {
    GstPropsEntry *entry2;
    entry2 = (GstPropsEntry *)sinklist->data;
    missing++;
  }
end:

  if (missing)
    return FALSE;

  return compatible;
}

static GstPropsEntry*
gst_props_entry_intersect (GstPropsEntry *entry1, GstPropsEntry *entry2)
{
  GstPropsEntry *result = NULL;

  /* try to move the ranges and lists first */
  switch (entry2->propstype) {
    case GST_PROPS_INT_RANGE_TYPE:
    case GST_PROPS_FLOAT_RANGE_TYPE:
    case GST_PROPS_LIST_TYPE:
    {
      GstPropsEntry *temp;

      temp = entry1;
      entry1 = entry2;
      entry2 = temp;
    }
    default:
      break;
  }

  switch (entry1->propstype) {
    case GST_PROPS_LIST_TYPE:
    {
      GList *entrylist = entry1->data.list_data.entries;
      GList *intersection = NULL;

      while (entrylist) {
	GstPropsEntry *entry = (GstPropsEntry *) entrylist->data;
	GstPropsEntry *intersectentry;

	intersectentry = gst_props_entry_intersect (entry2, entry);

	if (intersectentry) {
	  if (intersectentry->propstype == GST_PROPS_LIST_TYPE) {
	    intersection = g_list_concat (intersection, 
			       intersectentry->data.list_data.entries);
	    /* set the list to NULL because the entries are concatenated to the above
	     * list and we don't want to free them */
	    intersectentry->data.list_data.entries = NULL;
	    gst_props_entry_destroy (intersectentry);
	  }
	  else {
	    intersection = g_list_prepend (intersection, intersectentry);
	  }
	}
	entrylist = g_list_next (entrylist);
      }
      if (intersection) {
	/* check if the list only contains 1 element, if so, we can just copy it */
	if (g_list_next (intersection) == NULL) {
	  result = (GstPropsEntry *) (intersection->data); 
	  g_list_free (intersection);
	}
	/* else we need to create a new entry to hold the list */
	else {
	  result = gst_props_alloc_entry ();
	  result->propid = entry1->propid;
	  result->propstype = GST_PROPS_LIST_TYPE;
	  result->data.list_data.entries = g_list_reverse (intersection);
	}
      }
      return result;
    }
    case GST_PROPS_INT_RANGE_TYPE:
      switch (entry2->propstype) {
	/* a - b   <--->   a - c */
        case GST_PROPS_INT_RANGE_TYPE:
        {
	  gint lower = MAX (entry1->data.int_range_data.min, entry2->data.int_range_data.min);
	  gint upper = MIN (entry1->data.int_range_data.max, entry2->data.int_range_data.max);

	  if (lower <= upper) {
            result = gst_props_alloc_entry ();
	    result->propid = entry1->propid;

	    if (lower == upper) {
	      result->propstype = GST_PROPS_INT_TYPE;
	      result->data.int_data = lower;
	    }
	    else {
	      result->propstype = GST_PROPS_INT_RANGE_TYPE;
	      result->data.int_range_data.min = lower;
	      result->data.int_range_data.max = upper;
	    }
	  }
	  break;
	}
        case GST_PROPS_LIST_TYPE:
        {
          GList *entries = entry2->data.list_data.entries;
          result = gst_props_alloc_entry ();
          result->propid = entry1->propid;
          result->propstype = GST_PROPS_LIST_TYPE;
          result->data.list_data.entries = NULL;
          while (entries) {
            GstPropsEntry *this = (GstPropsEntry *)entries->data;
            if (this->propstype != GST_PROPS_INT_TYPE) {
              /* no hope, this list doesn't even contain ints! */
              gst_props_entry_destroy (result);
              result = NULL;
              break;
            }
            if (this->data.int_data >= entry1->data.int_range_data.min &&
                this->data.int_data <= entry1->data.int_range_data.max)
	    {
	      /* prepend and reverse at the end */
              result->data.list_data.entries = g_list_prepend (result->data.list_data.entries,
                                                               gst_props_entry_copy (this));
            }
            entries = g_list_next (entries);
          }
	  if (result) {
	    result->data.list_data.entries = g_list_reverse (result->data.list_data.entries);
	  }
          break;
        }
        case GST_PROPS_INT_TYPE:
        {
	  if (entry1->data.int_range_data.min <= entry2->data.int_data && 
	      entry1->data.int_range_data.max >= entry2->data.int_data) 
	  {
            result = gst_props_entry_copy (entry2);
	  }
          break;
        }
        default:
	  break;
      }
      break;
    case GST_PROPS_FLOAT_RANGE_TYPE:
      switch (entry2->propstype) {
	/* a - b   <--->   a - c */
        case GST_PROPS_FLOAT_RANGE_TYPE:
        {
	  gfloat lower = MAX (entry1->data.float_range_data.min, entry2->data.float_range_data.min);
	  gfloat upper = MIN (entry1->data.float_range_data.max, entry2->data.float_range_data.max);

	  if (lower <= upper) {
            result = gst_props_alloc_entry ();
	    result->propid = entry1->propid;

	    if (lower == upper) {
	      result->propstype = GST_PROPS_FLOAT_TYPE;
	      result->data.float_data = lower;
	    }
	    else {
	      result->propstype = GST_PROPS_FLOAT_RANGE_TYPE;
	      result->data.float_range_data.min = lower;
	      result->data.float_range_data.max = upper;
	    }
	  }
	  break;
	}
        case GST_PROPS_FLOAT_TYPE:
	  if (entry1->data.float_range_data.min <= entry2->data.float_data && 
	      entry1->data.float_range_data.max >= entry2->data.float_data) 
	  {
            result = gst_props_entry_copy (entry2);
	  }
        default:
	  break;
      }
      break;
    case GST_PROPS_FOURCC_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a */
        case GST_PROPS_FOURCC_TYPE:
          if (entry1->data.fourcc_data == entry2->data.fourcc_data)
	    result = gst_props_entry_copy (entry1);
        default:
	  break;
      }
      break;
    case GST_PROPS_INT_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a */
        case GST_PROPS_INT_TYPE:
          if (entry1->data.int_data == entry2->data.int_data)
	    result = gst_props_entry_copy (entry1);
        default:
	  break;
      }
      break;
    case GST_PROPS_FLOAT_TYPE:
      switch (entry2->propstype) {
	/* b   <--->   a */
        case GST_PROPS_FLOAT_TYPE:
          if (entry1->data.float_data == entry2->data.float_data)
	    result = gst_props_entry_copy (entry1);
        default:
	  break;
      }
      break;
    case GST_PROPS_BOOLEAN_TYPE:
      switch (entry2->propstype) {
	/* t   <--->   t */
        case GST_PROPS_BOOLEAN_TYPE:
          if (entry1->data.bool_data == entry2->data.bool_data)
	    result = gst_props_entry_copy (entry1);
        default:
	  break;
      }
    case GST_PROPS_STRING_TYPE:
      switch (entry2->propstype) {
	/* t   <--->   t */
        case GST_PROPS_STRING_TYPE:
          if (!strcmp (entry1->data.string_data.string, entry2->data.string_data.string))
	    result = gst_props_entry_copy (entry1);
        default:
	  break;
      }
    default:
      break;
  }

  return result;
}

/* when running over the entries in sorted order we can
 * optimize addition with _prepend and a reverse at the end */
#define gst_props_entry_add_sorted_prepend(props, entry) 		\
G_STMT_START {						 		\
  /* avoid double evaluation of input */				\
  GstPropsEntry *toadd = (entry);					\
  if (GST_PROPS_ENTRY_IS_VARIABLE (toadd))		 		\
    GST_PROPS_FLAG_UNSET ((props), GST_PROPS_FIXED);			\
  props->properties = g_list_prepend ((props)->properties, toadd);	\
} G_STMT_END

/**
 * gst_props_intersect:
 * @props1: a property
 * @props2: another property
 *
 * Calculates the intersection bewteen two GstProps.
 *
 * Returns: a GstProps with the intersection or NULL if the 
 * intersection is empty. The new GstProps is floating and must
 * be unreffed afetr use.
 */
GstProps*
gst_props_intersect (GstProps *props1, GstProps *props2)
{
  GList *props1list;
  GList *props2list;
  GstProps *intersection;
  GList *leftovers;
  GstPropsEntry *iprops = NULL;

  g_return_val_if_fail (props1 != NULL, NULL);
  g_return_val_if_fail (props2 != NULL, NULL);

  intersection = gst_props_empty_new ();

  props1list = props1->properties;
  props2list = props2->properties;

  while (props1list && props2list) {
    GstPropsEntry *entry1;
    GstPropsEntry *entry2;

    entry1 = (GstPropsEntry *)props1list->data;
    entry2 = (GstPropsEntry *)props2list->data;

    while (entry1->propid < entry2->propid) {
      gst_props_entry_add_sorted_prepend (intersection, gst_props_entry_copy (entry1));

      props1list = g_list_next (props1list);
      if (!props1list)
	goto end;

      entry1 = (GstPropsEntry *)props1list->data;
    }
    while (entry1->propid > entry2->propid) {
      gst_props_entry_add_sorted_prepend (intersection, gst_props_entry_copy (entry2));

      props2list = g_list_next (props2list);
      if (!props2list)
	goto end;

      entry2 = (GstPropsEntry *)props2list->data;
    }
    /* at this point we are talking about the same property */
    iprops = gst_props_entry_intersect (entry1, entry2);
    if (!iprops) {
      /* common properties did not intersect, intersection is empty */
      gst_props_unref (intersection);
      return NULL;
    }

    gst_props_entry_add_sorted_prepend (intersection, iprops);

    props1list = g_list_next (props1list);
    props2list = g_list_next (props2list);
  }

end:
  /* at this point one of the lists could contain leftover properties, while
   * the other one is NULL */
  leftovers = props1list;
  if (!leftovers)
    leftovers = props2list;

  while (leftovers) {
    gst_props_entry_add_sorted_prepend (intersection,
		         gst_props_entry_copy ((GstPropsEntry *) leftovers->data));
    leftovers = g_list_next (leftovers);
  }

  intersection->properties = g_list_reverse (intersection->properties);

  return intersection;
}

/**
 * gst_props_normalize:
 * @props: a property
 *
 * Unrolls all lists in the given GstProps. This is usefull if you
 * want to loop over the props.
 *
 * Returns: A GList with the unrolled props entries. g_list_free 
 * after usage.
 */
GList*
gst_props_normalize (GstProps *props)
{
  GList *entries;
  GList *result = NULL;
  gboolean fixed = TRUE;

  if (!props)
    return NULL;

  /* warning: the property here could have a wrong FIXED flag
   * but it'll be fixed at the end of the loop */
  entries = props->properties;

  while (entries) {
    GstPropsEntry *entry = (GstPropsEntry *) entries->data;

    /* be carefull with the bitmasks */
    fixed &= (GST_PROPS_ENTRY_IS_VARIABLE (entry) ? FALSE : TRUE);

    if (entry->propstype == GST_PROPS_LIST_TYPE) {
      GList *list_entries = entry->data.list_data.entries;

      while (list_entries) {
        GstPropsEntry *list_entry = (GstPropsEntry *) list_entries->data;
        GstPropsEntry *new_entry;
	GstProps *newprops;
	GList *lentry;

	newprops = gst_props_copy (props);
        lentry = g_list_find_custom (newprops->properties, 
			             GINT_TO_POINTER (list_entry->propid), 
				     props_find_func);
	if (lentry) {
          GList *new_list;

          new_entry = (GstPropsEntry *) lentry->data;
	  memcpy (new_entry, list_entry, sizeof (GstPropsEntry));
	  /* it's possible that this property now became fixed, since we
	   * removed the list, we'll update the flag when everything is
	   * unreolled at the end of this function */
	  new_list = gst_props_normalize (newprops);
          result = g_list_concat (new_list, result);
	}
	else {
          /* FIXME append or prepend */
          result = g_list_append (result, newprops);
	}
	
        list_entries = g_list_next (list_entries);
      }
      /* we break out of the loop because the other lists are
       * unrolled in the recursive call */
      break;
    }
    entries = g_list_next (entries);
  }
  if (!result) {
    /* at this point, the props did not need any unrolling, we have scanned
     * all entries and the fixed flag will hold the correct value */
    if (fixed)
      GST_PROPS_FLAG_SET (props, GST_PROPS_FIXED);
    else
      GST_PROPS_FLAG_UNSET (props, GST_PROPS_FIXED);

    /* no result, create list with input props */
    result = g_list_prepend (result, props);
  }
  else {
    result = g_list_reverse (result);
  }
  return result;
}

#ifndef GST_DISABLE_LOADSAVE_REGISTRY
static xmlNodePtr
gst_props_save_thyself_func (GstPropsEntry *entry, xmlNodePtr parent)
{
  xmlNodePtr subtree;
  gchar *str;

  switch (entry->propstype) {
    case GST_PROPS_INT_TYPE:
      subtree = xmlNewChild (parent, NULL, "int", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      str = g_strdup_printf ("%d", entry->data.int_data);
      xmlNewProp (subtree, "value", str);
      g_free(str);
      break;
    case GST_PROPS_INT_RANGE_TYPE:
      subtree = xmlNewChild (parent, NULL, "range", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      str = g_strdup_printf ("%d", entry->data.int_range_data.min);
      xmlNewProp (subtree, "min", str);
      g_free(str);
      str = g_strdup_printf ("%d", entry->data.int_range_data.max);
      xmlNewProp (subtree, "max", str);
      g_free(str);
      break;
    case GST_PROPS_FLOAT_TYPE:
      subtree = xmlNewChild (parent, NULL, "float", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      str = g_strdup_printf ("%f", entry->data.float_data);
      xmlNewProp (subtree, "value", str);
      g_free(str);
      break;
    case GST_PROPS_FLOAT_RANGE_TYPE:
      subtree = xmlNewChild (parent, NULL, "floatrange", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      str = g_strdup_printf ("%f", entry->data.float_range_data.min);
      xmlNewProp (subtree, "min", str);
      g_free(str);
      str = g_strdup_printf ("%f", entry->data.float_range_data.max);
      xmlNewProp (subtree, "max", str);
      g_free(str);
      break;
    case GST_PROPS_FOURCC_TYPE: 
      str = g_strdup_printf ("%c%c%c%c",
	(entry->data.fourcc_data>>0)&0xff,
	(entry->data.fourcc_data>>8)&0xff,
	(entry->data.fourcc_data>>16)&0xff,
	(entry->data.fourcc_data>>24)&0xff);
      xmlAddChild (parent, xmlNewComment (str));
      g_free(str);
      subtree = xmlNewChild (parent, NULL, "fourcc", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      str = g_strdup_printf ("%08x", entry->data.fourcc_data);
      xmlNewProp (subtree, "hexvalue", str);
      g_free(str);
      break;
    case GST_PROPS_BOOLEAN_TYPE:
      subtree = xmlNewChild (parent, NULL, "boolean", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      xmlNewProp (subtree, "value", (entry->data.bool_data ?  "true" : "false"));
      break;
    case GST_PROPS_STRING_TYPE:
      subtree = xmlNewChild (parent, NULL, "string", NULL);
      xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
      xmlNewProp (subtree, "value", entry->data.string_data.string);
      break;
    default:
      g_warning ("trying to save unknown property type %d", entry->propstype);
      break;
  }

  return parent;
}

/**
 * gst_props_save_thyself:
 * @props: a property to save
 * @parent: the parent XML tree
 *
 * Saves the property into an XML representation.
 *
 * Returns: the new XML tree
 */
xmlNodePtr
gst_props_save_thyself (GstProps *props, xmlNodePtr parent)
{
  GList *proplist;
  xmlNodePtr subtree;

  g_return_val_if_fail (props != NULL, NULL);

  proplist = props->properties;

  while (proplist) {
    GstPropsEntry *entry = (GstPropsEntry *) proplist->data;

    switch (entry->propstype) {
      case GST_PROPS_LIST_TYPE: 
        subtree = xmlNewChild (parent, NULL, "list", NULL);
        xmlNewProp (subtree, "name", g_quark_to_string (entry->propid));
        g_list_foreach (entry->data.list_data.entries, (GFunc) gst_props_save_thyself_func, subtree);
	break;
      default:
    	gst_props_save_thyself_func (entry, parent);
    }

    proplist = g_list_next (proplist);
  }

  return parent;
}

static GstPropsEntry*
gst_props_load_thyself_func (xmlNodePtr field)
{
  GstPropsEntry *entry;
  gchar *prop;

  entry = gst_props_alloc_entry ();

  if (!strcmp(field->name, "int")) {
    entry->propstype = GST_PROPS_INT_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp(field, "value");
    sscanf (prop, "%d", &entry->data.int_data);
    g_free (prop);
  }
  else if (!strcmp(field->name, "range")) {
    entry->propstype = GST_PROPS_INT_RANGE_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp (field, "min");
    sscanf (prop, "%d", &entry->data.int_range_data.min);
    g_free (prop);
    prop = xmlGetProp (field, "max");
    sscanf (prop, "%d", &entry->data.int_range_data.max);
    g_free (prop);
  }
  else if (!strcmp(field->name, "float")) {
    entry->propstype = GST_PROPS_FLOAT_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp(field, "value");
    sscanf (prop, "%f", &entry->data.float_data);
    g_free (prop);
  }
  else if (!strcmp(field->name, "floatrange")) {
    entry->propstype = GST_PROPS_FLOAT_RANGE_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp (field, "min");
    sscanf (prop, "%f", &entry->data.float_range_data.min);
    g_free (prop);
    prop = xmlGetProp (field, "max");
    sscanf (prop, "%f", &entry->data.float_range_data.max);
    g_free (prop);
  }
  else if (!strcmp(field->name, "boolean")) {
    entry->propstype = GST_PROPS_BOOLEAN_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp (field, "value");
    if (!strcmp (prop, "false")) entry->data.bool_data = 0;
    else entry->data.bool_data = 1;
    g_free (prop);
  }
  else if (!strcmp(field->name, "fourcc")) {
    entry->propstype = GST_PROPS_FOURCC_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    prop = xmlGetProp (field, "hexvalue");
    sscanf (prop, "%08x", &entry->data.fourcc_data);
    g_free (prop);
  }
  else if (!strcmp(field->name, "string")) {
    entry->propstype = GST_PROPS_STRING_TYPE;
    prop = xmlGetProp(field, "name");
    entry->propid = g_quark_from_string (prop);
    g_free (prop);
    entry->data.string_data.string = xmlGetProp (field, "value");
  }
  else {
    gst_props_entry_destroy (entry);
    entry = NULL;
  }

  return entry;
}

/**
 * gst_props_load_thyself:
 * @parent: the XML tree to load from
 *
 * Creates a new property out of an XML tree.
 *
 * Returns: the new property
 */
GstProps*
gst_props_load_thyself (xmlNodePtr parent)
{
  GstProps *props;
  xmlNodePtr field = parent->xmlChildrenNode;
  gchar *prop;

  props = gst_props_empty_new ();

  while (field) {
    if (!strcmp (field->name, "list")) {
      GstPropsEntry *entry;
      xmlNodePtr subfield = field->xmlChildrenNode;

      entry = gst_props_alloc_entry ();
      prop = xmlGetProp (field, "name");
      entry->propid = g_quark_from_string (prop);
      g_free (prop);
      entry->propstype = GST_PROPS_LIST_TYPE;
      entry->data.list_data.entries = NULL;

      while (subfield) {
        GstPropsEntry *subentry = gst_props_load_thyself_func (subfield);

	if (subentry)
	  entry->data.list_data.entries = g_list_prepend (entry->data.list_data.entries, subentry);

        subfield = subfield->next;
      }
      entry->data.list_data.entries = g_list_reverse (entry->data.list_data.entries);
      gst_props_add_entry (props, entry);
    }
    else {
      GstPropsEntry *entry;

      entry = gst_props_load_thyself_func (field);

      if (entry)
        gst_props_add_entry (props, entry);
    }
    field = field->next;
  }

  return props;
}
#endif /* GST_DISABLE_LOADSAVE_REGISTRY */

