/*
 * SwamiPropTree.c - Swami property tree object
 *
 * 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 <glib.h>
#include <glib-object.h>

#include "SwamiPropTree.h"
#include "SwamiControlProp.h"
#include "SwamiLog.h"
#include "swami_priv.h"


/* a cached active property value */
typedef struct
{
    GParamSpec *pspec;	 /* parameter spec for this cached property */
    SwamiControl *prop_ctrl; /* object property control for this cache */
    SwamiPropTreeValue *value; /* tree value connected to prop_ctrl */
    SwamiPropTreeNode *value_node; /* node containing @value */
} CacheValue;


/* unlocked chunk alloc/free macros (requires external locking) */
#define swami_prop_tree_new_node_L() \
  g_slice_new (SwamiPropTreeNode)
#define swami_prop_tree_free_node_L(node) \
  g_slice_free (SwamiPropTreeNode, node)

#define swami_prop_tree_new_value_L() \
  g_slice_new (SwamiPropTreeValue)
#define swami_prop_tree_free_value_L(value) \
  g_slice_free (SwamiPropTreeValue, value)

#define TREE_CACHE_PREALLOC 64
#define swami_prop_tree_new_cache_L() \
  g_slice_new (CacheValue)
#define swami_prop_tree_free_cache_L(cache) \
  g_slice_free (CacheValue, cache)

static void swami_prop_tree_class_init(SwamiPropTreeClass *klass);
static void swami_prop_tree_init(SwamiPropTree *proptree);
static void swami_prop_tree_finalize(GObject *object);

static void swami_prop_tree_object_weak_notify(gpointer user_data,
        GObject *object);
static inline void swami_prop_tree_node_reset_L(SwamiPropTree *proptree,
        SwamiPropTreeNode *treenode);
static inline void swami_prop_tree_node_clear_cache_L
(SwamiPropTreeNode *treenode);

static void recursive_remove_nodes(GNode *node, SwamiPropTree *proptree);

static void resolve_object_props_L(SwamiPropTree *proptree,
                                   GNode *object_node, GList *speclist);
static GList *object_spec_list(GObject *object);
static void refresh_value_nodes_L(GNode *node, SwamiPropTreeValue *treeval);
static void refresh_value_nodes_list_L(GNode *node, GSList *treevals);
static inline void refresh_cache_value_L(GNode *node, CacheValue *cache);

static GObjectClass *parent_class = NULL;


GType
swami_prop_tree_get_type(void)
{
    static GType otype = 0;

    if(!otype)
    {
        static const GTypeInfo type_info =
        {
            sizeof(SwamiPropTreeClass), NULL, NULL,
            (GClassInitFunc) swami_prop_tree_class_init,
            (GClassFinalizeFunc) NULL, NULL,
            sizeof(SwamiPropTree), 0,
            (GInstanceInitFunc) swami_prop_tree_init
        };

        otype = g_type_register_static(SWAMI_TYPE_LOCK, "SwamiPropTree",
                                       &type_info, 0);
    }

    return (otype);
}

static void
swami_prop_tree_class_init(SwamiPropTreeClass *klass)
{
    GObjectClass *obj_class = G_OBJECT_CLASS(klass);

    parent_class = g_type_class_peek_parent(klass);

    obj_class->finalize = swami_prop_tree_finalize;
}

static void
swami_prop_tree_init(SwamiPropTree *proptree)
{
    proptree->tree = NULL;
    proptree->object_hash = g_hash_table_new(NULL, NULL);
}

static void
swami_prop_tree_finalize(GObject *object)
{
    SwamiPropTree *proptree = SWAMI_PROP_TREE(object);

    SWAMI_LOCK_WRITE(proptree);

    if(proptree->tree)
    {
        recursive_remove_nodes(proptree->tree, proptree);    /* recursive remove */
    }

    g_hash_table_destroy(proptree->object_hash);

    SWAMI_UNLOCK_WRITE(proptree);

    if(parent_class->finalize)
    {
        parent_class->finalize(object);
    }
}

/**
 * swami_prop_tree_new:
 *
 * Create a new property tree object.
 *
 * Returns: New property tree object with a refcount of 1.
 */
SwamiPropTree *
swami_prop_tree_new(void)
{
    return (SWAMI_PROP_TREE(g_object_new(SWAMI_TYPE_PROP_TREE, NULL)));
}

/**
 * swami_prop_tree_set_root:
 * @proptree: Property tree object
 * @root: Object to make the root object of the tree
 *
 * Set the root object of a property tree. Should only be set once.
 */
void
swami_prop_tree_set_root(SwamiPropTree *proptree, GObject *root)
{
    SwamiPropTreeNode *node;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(root));

    SWAMI_LOCK_WRITE(proptree);

    if(swami_log_if_fail(proptree->tree == NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    node = swami_prop_tree_new_node_L();
    node->object = root;
    node->values = NULL;
    node->cache = NULL;
    proptree->tree = g_node_new(node);

    /* add to object => GNode hash */
    g_hash_table_insert(proptree->object_hash, root, proptree->tree);

    /* weak ref to passively catch objects demise */
    g_object_weak_ref(root, swami_prop_tree_object_weak_notify, proptree);

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_prepend:
 * @proptree: Property tree object
 * @parent: Object in @proptree to parent to
 * @obj: Object to prepend to @proptree
 *
 * Prepends an object to a property tree.
 * (object isn't owned by root proptree.)
 */
void
swami_prop_tree_prepend(SwamiPropTree *proptree, GObject *parent,
                        GObject *obj)
{
    SwamiPropTreeNode *treenode;
    GNode *parent_node, *newnode;
    GList *speclist;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(parent));
    g_return_if_fail(G_IS_OBJECT(obj));


    SWAMI_LOCK_WRITE(proptree);

    parent_node = g_hash_table_lookup(proptree->object_hash, parent);

    if(swami_log_if_fail(parent_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    treenode = swami_prop_tree_new_node_L();
    treenode->object = obj;
    treenode->values = NULL;
    treenode->cache = NULL;
    newnode = g_node_prepend_data(parent_node, treenode);

    g_hash_table_insert(proptree->object_hash, obj, newnode);

    /* weak ref to passively catch objects demise */
    g_object_weak_ref(obj, swami_prop_tree_object_weak_notify, proptree);

    /* Build a GList of object's pspec properties */
    speclist = object_spec_list(obj);
    /* resolve properties if any (speclist is freed) */
    if(speclist)
    {
        resolve_object_props_L(proptree, newnode, speclist);
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_insert_before:
 * @proptree: Property tree object
 * @parent: Object in @proptree to parent to
 * @sibling: Object in @proptree to insert before or %NULL to append
 * @obj: Object to prepend to @proptree
 *
 * Inserts an object to a property tree before @sibling and parented to
 * @parent.
 * (object isn't owned by root proptree.)
 */
void
swami_prop_tree_insert_before(SwamiPropTree *proptree, GObject *parent,
                              GObject *sibling, GObject *obj)
{
    SwamiPropTreeNode *treenode;
    GNode *parent_node, *sibling_node = NULL, *newnode;
    GList *speclist;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(parent));
    g_return_if_fail(!sibling || G_IS_OBJECT(sibling));
    g_return_if_fail(G_IS_OBJECT(obj));

    SWAMI_LOCK_WRITE(proptree);

    parent_node = g_hash_table_lookup(proptree->object_hash, parent);

    if(swami_log_if_fail(parent_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    if(sibling)
    {
        sibling_node = g_hash_table_lookup(proptree->object_hash, sibling);

        if(swami_log_if_fail(sibling_node != NULL))
        {
            SWAMI_UNLOCK_WRITE(proptree);
            return;
        }
    }

    treenode = swami_prop_tree_new_node_L();
    treenode->object = obj;
    treenode->values = NULL;
    treenode->cache = NULL;
    newnode = g_node_insert_data_before(parent_node, sibling_node, treenode);

    g_hash_table_insert(proptree->object_hash, obj, newnode);

    /* weak ref to passively catch objects demise */
    g_object_weak_ref(obj, swami_prop_tree_object_weak_notify, proptree);

    /* Build a GList of object's pspec properties */
    speclist = object_spec_list(obj);

    /* resolve properties if any (speclist is freed) */
    if(speclist)
    {
        resolve_object_props_L(proptree, newnode, speclist);
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_remove:
 * @proptree: Property tree object
 * @obj: Object in @proptree to remove
 *
 * Removes an @obj, and all values bound to it, from a property tree.
 * All child nodes are moved up to the next parent node.
 */
void
swami_prop_tree_remove(SwamiPropTree *proptree, GObject *obj)
{
    SwamiPropTreeNode *treenode;
    GNode *obj_node, *newparent, *n, *temp;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(obj));

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, obj);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    newparent = obj_node->parent;

    if(swami_log_if_fail(newparent != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    treenode = (SwamiPropTreeNode *)(obj_node->data);
    g_node_unlink(obj_node);	/* unlink the GNode */

    n = g_node_last_child(obj_node);

    while(n)  /* move children of removed node to parent of it and refresh */
    {
        temp = n;
        n = n->prev;

        /* children node temp is parented to newparent */
        g_node_prepend(newparent, temp);

        /* recursive refresh */
        if(treenode->values)
        {
            /* update temp node (and all its descendant children) */
            refresh_value_nodes_list_L(temp, treenode->values);
        }
    }

    obj_node->children = NULL;
    g_node_destroy(obj_node);	/* destroy the GNode */

    /* reset and free the tree node */
    swami_prop_tree_node_reset_L(proptree, treenode);
    swami_prop_tree_free_node_L(treenode);

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_remove_recursive:
 * @proptree: Property tree object
 * @obj: Object in @proptree to recursively remove
 *
 * Recursively removes an @object, and all values bound to it, from a property
 * tree.
 */
void
swami_prop_tree_remove_recursive(SwamiPropTree *proptree, GObject *obj)
{
    GNode *obj_node;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(obj));

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, obj);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    recursive_remove_nodes(obj_node, proptree);  /* recursive remove */

    if(obj_node == proptree->tree)
    {
        proptree->tree = NULL;
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/* called when an object in the tree is destroyed */
static void
swami_prop_tree_object_weak_notify(gpointer user_data, GObject *object)
{
    SwamiPropTree *proptree = SWAMI_PROP_TREE(user_data);
    GNode *obj_node;

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, object);

    if(obj_node && proptree->tree)
    {
        /*
          we are currently in the callback weak_notify (on finalization of object).
          Now this callback doesn't exist anymore inside GObject library, so we set object to NULL
          to indicate that swami_prop_tree_node_reset_L() should not call g_object_weak_unref().

          As object is set to NULL, we need to remove object out of the hash table here because
          this could not be done in swami_prop_tree_node_reset_L().
        */
        SwamiPropTreeNode *treenode = (SwamiPropTreeNode *) obj_node->data;
        g_hash_table_remove (proptree->object_hash, treenode->object);
        treenode->object = NULL;
        recursive_remove_nodes(obj_node, proptree);  /* recursive remove */

        if(obj_node == proptree->tree)
        {
            proptree->tree = NULL;
        }
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/* reset a SwamiPropTreeNode (remove all of its innards) */
static inline void
swami_prop_tree_node_reset_L(SwamiPropTree *proptree,
                             SwamiPropTreeNode *treenode)
{
    GSList *p;

    if(treenode->object)	        /* clear object->treenode hash entry */
    {
        g_object_weak_unref(treenode->object,  /* -- remove the weak ref */
                            swami_prop_tree_object_weak_notify, proptree);
        g_hash_table_remove(proptree->object_hash, treenode->object);
    }

    p = treenode->values;

    while(p)			/* destroy values */
    {
        /* free name and control before destroying value */
        SwamiPropTreeValue *treeval = (SwamiPropTreeValue *)(p->data);

        g_free (treeval->prop_name);

        if(treeval->control)
        {
            g_object_unref (treeval->control); /* -- unref control */
        }

        /* free the tree value */
        swami_prop_tree_free_value_L(p->data);
        p = g_slist_delete_link(p, p);
    }

    swami_prop_tree_node_clear_cache_L(treenode);
}

/* clear cache of a property tree node */
static inline void
swami_prop_tree_node_clear_cache_L(SwamiPropTreeNode *treenode)
{
    CacheValue *cache;
    GSList *p;

    p = treenode->cache;

    while(p)			/* destroy cache */
    {
        cache = (CacheValue *)(p->data);

        if(cache->prop_ctrl)     /* destroy property control */
        {
            swami_control_disconnect_all((SwamiControl *)(cache->prop_ctrl));
            g_object_unref(cache->prop_ctrl);  /* -- unref from cache */
        }

        swami_prop_tree_free_cache_L(p->data);  /* free cache value */
        p = g_slist_delete_link(p, p);
    }

    treenode->cache = NULL;
}

/* recursively remove SwamiPropTreeNodes */
static void
recursive_remove_nodes(GNode *node, SwamiPropTree *proptree)
{
    GNode *n;

    n = node->children;

    while(n)
    {
        recursive_remove_nodes(n, proptree);
        n = n->next;
    }

    {
        SwamiPropTreeNode *treenode = (SwamiPropTreeNode *)(node->data);

        swami_prop_tree_node_reset_L(proptree, treenode);
        swami_prop_tree_free_node_L(treenode);  /* free node */
        g_node_destroy(node);
    }
}

/**
 * swami_prop_tree_replace:
 * @proptree: Property tree object
 * @old: Old object in @proptree to replace
 * @new: New object to replace @old object with
 *
 * Replaces an @old object with a @new object in a property tree.
 */
void
swami_prop_tree_replace(SwamiPropTree *proptree, GObject *old, GObject *new)
{
    SwamiPropTreeNode *treenode;
    GNode *obj_node;
    GList *speclist;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(old));
    g_return_if_fail(G_IS_OBJECT(new));

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, old);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    treenode = (SwamiPropTreeNode *)(obj_node->data);

    /* clear old cache and remove old object hash entry */
    swami_prop_tree_node_clear_cache_L(treenode);
    g_hash_table_remove(proptree->object_hash, old);

    g_hash_table_insert(proptree->object_hash, new, obj_node);

    /* weak ref to passively catch objects demise */
    g_object_weak_ref(new, swami_prop_tree_object_weak_notify, proptree);

    /* get GParamSpec list for new object. speclist is allocated (if any spec) */
    speclist = object_spec_list(new);

    /* re-resolve properties if any (speclist is freed) */
    if(speclist)
    {
        resolve_object_props_L(proptree, obj_node, speclist);
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_get_children:
 * @proptree: Property tree object
 * @obj: Object in @proptree to get children of
 *
 * Gets the list of GObject children of @obj in a property tree.
 *
 * Returns: A new object list populated with the children of @obj in @proptree.
 * The new list has a reference count of 1 which the caller owns, remember to
 * unref it when finished.
 */
IpatchList *
swami_prop_tree_get_children(SwamiPropTree *proptree, GObject *obj)
{
    IpatchList *list;
    SwamiPropTreeNode *treenode;
    GNode *obj_node, *n;

    g_return_val_if_fail(SWAMI_IS_PROP_TREE(proptree), NULL);
    g_return_val_if_fail(G_IS_OBJECT(obj), NULL);

    list = ipatch_list_new();	/* ++ ref new list */

    SWAMI_LOCK_READ(proptree);
    obj_node = g_hash_table_lookup(proptree->object_hash, obj);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_READ(proptree);
        g_object_unref(list);	/* -- unref list */
        return (NULL);
    }

    n = obj_node->children;

    while(n)
    {
        treenode = (SwamiPropTreeNode *)(n->data);
        g_object_ref(treenode->object);  /* ++ ref for list */
        list->items = g_list_prepend(list->items, treenode->object);
        n = n->next;
    }

    SWAMI_UNLOCK_READ(proptree);

    list->items = g_list_reverse(list->items);

    return (list);
}

/**
 * swami_prop_tree_get_node:
 * @proptree: Property tree object
 * @obj: Object in @proptree to get GNode of
 *
 * Gets the GNode of an object in a property tree. This should only be done
 * for objects which are sure to remain in the property tree for the duration
 * of GNode use.
 *
 * Returns: GNode of @obj in property tree. GNode can only be used for as
 * long as the object is in the tree.
 */
GNode *
swami_prop_tree_object_get_node(SwamiPropTree *proptree, GObject *obj)
{
    GNode *node;

    g_return_val_if_fail(SWAMI_IS_PROP_TREE(proptree), NULL);
    g_return_val_if_fail(G_IS_OBJECT(obj), NULL);

    SWAMI_LOCK_READ(proptree);
    node = g_hash_table_lookup(proptree->object_hash, obj);
    SWAMI_UNLOCK_READ(proptree);

    return (node);
}

/**
 * swami_prop_tree_add_value:
 * @proptree: Property tree object
 * @obj: Object in @proptree
 * @prop_type: GObject derived type the value should match (0 = wildcard)
 * @prop_name: Property name to match
 * @control: Active value control
 *
 * Adds a value to an object in a property tree. If a value already exists
 * with the same @prop_type and @prop_name its @control value is replaced.
 */
void
swami_prop_tree_add_value(SwamiPropTree *proptree, GObject *obj,
                          GType prop_type, const char *prop_name,
                          SwamiControl *control)
{
    SwamiPropTreeValue *treeval;
    SwamiPropTreeNode *treenode;
    GNode *obj_node;
    GSList *p;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(obj));
    g_return_if_fail(!prop_type || g_type_is_a(prop_type, G_TYPE_OBJECT));
    g_return_if_fail(prop_name != NULL && *prop_name != '\0');
    g_return_if_fail(SWAMI_IS_CONTROL(control));

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, obj);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    treenode = (SwamiPropTreeNode *)(obj_node->data);

    g_object_ref(control);	/* ++ ref control for tree value */

    p = treenode->values;

    while(p)		   /* look for existing duplicate tree value */
    {
        treeval = (SwamiPropTreeValue *)(p->data);

        if(treeval->prop_type == prop_type
                && strcmp(treeval->prop_name, prop_name) == 0)
        {
            g_object_unref(treeval->control);  /* -- unref old control */
            break;
        }

        p = g_slist_next(p);
    }

    /* if a matching value doesn't already exist, allocate a new one */
    if(!p)
    {
        treeval = swami_prop_tree_new_value_L();
        treeval->prop_type = prop_type;
        treeval->prop_name = g_strdup(prop_name);
        treenode->values = g_slist_prepend(treenode->values, treeval);
    }

    treeval->control = control;

    refresh_value_nodes_L(obj_node, treeval);  /* refresh nodes */

    SWAMI_UNLOCK_WRITE(proptree);
}

/**
 * swami_prop_tree_remove_value:
 * @proptree: Property tree object
 * @obj: Object in @proptree
 * @prop_type: GObject derived type field of existing value
 * @prop_name: Property name field of existing value
 *
 * Removes a value from an object in a property tree. The @prop_type and
 * @prop_name parameters are used to find the value to remove.
 */
void
swami_prop_tree_remove_value(SwamiPropTree *proptree, GObject *obj,
                             GType prop_type, const char *prop_name)
{
    SwamiPropTreeValue *treeval;
    SwamiPropTreeNode *treenode;
    GNode *obj_node;
    GSList *p, *prev = NULL;

    g_return_if_fail(SWAMI_IS_PROP_TREE(proptree));
    g_return_if_fail(G_IS_OBJECT(obj));
    g_return_if_fail(!prop_type || g_type_is_a(prop_type, G_TYPE_OBJECT));
    g_return_if_fail(prop_name != NULL && *prop_name != '\0');

    SWAMI_LOCK_WRITE(proptree);

    obj_node = g_hash_table_lookup(proptree->object_hash, obj);

    if(swami_log_if_fail(obj_node != NULL))
    {
        SWAMI_UNLOCK_WRITE(proptree);
        return;
    }

    treenode = (SwamiPropTreeNode *)(obj_node->data);

    p = treenode->values;

    while(p)			/* loop over values */
    {
        treeval = (SwamiPropTreeValue *)(p->data);

        if(treeval->prop_type == prop_type  /* tree value matches? */
                && strcmp(treeval->prop_name, prop_name) == 0)
        {
            break;
        }

        prev = p;
        p = g_slist_next(p);
    }

    if(p)			/* if a match was found */
    {
        /* quick remove the value from the list */
        if(prev)
        {
            prev->next = p->next;
        }
        else
        {
            treenode->values = p->next;
        }

        refresh_value_nodes_L(obj_node, treeval);  /* refresh nodes */

        /* free the tree value */
        g_free(treeval->prop_name);
        g_object_unref(treeval->control);  /* -- unref control */
        swami_prop_tree_free_value_L(treeval);
    }

    SWAMI_UNLOCK_WRITE(proptree);
}

/* one time property resolve and cache function */
static void
resolve_object_props_L(SwamiPropTree *proptree, GNode *object_node,
                       GList *speclist)
{
    SwamiPropTreeNode *treenode, *obj_treenode;
    SwamiPropTreeValue *treeval;
    SwamiControl *prop_ctrl;
    CacheValue *cache;
    GParamSpec *pspec;
    GType obj_type;
    GNode *n;
    GList *sp, *temp;
    GSList *p;
    int flags;

    obj_treenode = (SwamiPropTreeNode *)(object_node->data);
    obj_type = G_TYPE_FROM_INSTANCE(obj_treenode->object);

    n = object_node;

    do
    {
        /* loop over tree ancestry */
        treenode = (SwamiPropTreeNode *)(n->data);
        p = treenode->values;

        while(p)			/* loop over variables in each node */
        {
            treeval = (SwamiPropTreeValue *)(p->data);

            /* object type matches? */
            if(!treeval->prop_type || (treeval->prop_type == obj_type))
            {
                sp = speclist;

                while(sp)   /* loop over remaining object param specs */
                {
                    pspec = (GParamSpec *)(sp->data);

                    if(strcmp(pspec->name, treeval->prop_name) == 0)
                    {
                        /* property name matches */
                        /* create a new object property control */
                        prop_ctrl = swami_get_control_prop_by_name
                                    (obj_treenode->object, pspec->name);

                        flags = SWAMI_CONTROL_CONN_INIT;

                        if(swami_control_get_flags(prop_ctrl)
                                & SWAMI_CONTROL_SENDS)
                        {
                            flags |= SWAMI_CONTROL_CONN_BIDIR;
                        }

                        /* connect the tree value control to the property control
                        and initialize the property to the tree value */
                        swami_control_connect(treeval->control,
                                              (SwamiControl *)prop_ctrl, flags);

                        /* create cache value and add it to object tree node */
                        cache = swami_prop_tree_new_cache_L();
                        cache->pspec = pspec;
                        cache->prop_ctrl = prop_ctrl;
                        cache->value = treeval;
                        cache->value_node = treenode;
                        obj_treenode->cache = g_slist_prepend
                                              (obj_treenode->cache, cache);

                        temp = sp;
                        sp = g_list_next(sp);

                        /* delete param spec from find spec list */
                        speclist = g_list_delete_link(speclist, temp);

                        if(!speclist)
                        {
                            goto done;    /* no more properties? */
                        }
                    }
                    else
                    {
                        sp = g_list_next(sp);
                    }
                }
            }

            p = g_slist_next(p);
        }
    }
    while((n = n->parent));

done:

    while(speclist)		/* loop over remaining parameter specs */
    {
        pspec = (GParamSpec *)(speclist->data);

        /* create an "unset" cache value */
        cache = swami_prop_tree_new_cache_L();
        cache->pspec = pspec;
        cache->prop_ctrl = NULL;
        cache->value = NULL;
        cache->value_node = NULL;
        obj_treenode->cache = g_slist_prepend(obj_treenode->cache, cache);

        /* free each node of the spec list */
        speclist = g_list_delete_link(speclist, speclist);
    }
}

/* create a GList of parameter specs for a given object */
static GList *
object_spec_list(GObject *object)
{
    GParamSpec **pspecs, **spp;
    GList *speclist = NULL;

    pspecs = g_object_class_list_properties(G_OBJECT_GET_CLASS(object), NULL);

    spp = pspecs;

    while(*spp)			/* create a param spec list */
    {
        speclist = g_list_prepend(speclist, *spp);
        spp++;
    }

    g_free(pspecs);		/* no longer need array */

    return (speclist);
}

/* recursively refresh cache values affected by a tree value */
static void
refresh_value_nodes_L(GNode *node, SwamiPropTreeValue *treeval)
{
    GNode *n;

    n = node->children;

    while(n)
    {
        refresh_value_nodes_L(n, treeval);
        n = n->next;
    }

    {
        SwamiPropTreeNode *treenode = (SwamiPropTreeNode *)(node->data);
        GType obj_type = G_TYPE_FROM_INSTANCE(treenode->object);
        char *prop_name = treeval->prop_name;
        CacheValue *cache;
        GSList *p;

        /* object type matches type criteria of treeval? */
        if(treeval->prop_type && treeval->prop_type != obj_type)
        {
            return;
        }

        p = treenode->cache;

        while(p)			/* loop over the node's cache */
        {
            cache = (CacheValue *)(p->data);

            if(strcmp(prop_name, cache->pspec->name) == 0)
            {
                break;
            }

            p = g_slist_next(p);
        }

        if(p)
        {
            refresh_cache_value_L(node, cache);
        }
    }
}

/* recursively refresh cache values affected by a list of tree values */
static void
refresh_value_nodes_list_L(GNode *node, GSList *treevals)
{
    GNode *n;

    n = node->children;

    while(n)
    {
        refresh_value_nodes_list_L(n, treevals);
        n = n->next;
    }

    {
        SwamiPropTreeNode *treenode = (SwamiPropTreeNode *)(node->data);
        GType obj_type = G_TYPE_FROM_INSTANCE(treenode->object);
        SwamiPropTreeValue *treeval;
        CacheValue *cache;
        GSList *p, *p2;

        p = treevals;

        while(p)  /* loop over list of tree values to match for refresh */
        {
            treeval = (SwamiPropTreeValue *)(p->data);

            /* treeval matches type criteria of treeval? */
            if(!treeval->prop_type || treeval->prop_type == obj_type)
            {
                p2 = treenode->cache;

                while(p2)			/* loop over the node's cache */
                {
                    cache = (CacheValue *)(p2->data);

                    /* prop name matches? - refresh cache val */
                    if(strcmp(treeval->prop_name, cache->pspec->name) == 0)
                    {
                        refresh_cache_value_L(node, cache);
                    }

                    p2 = g_slist_next(p2);
                }
            }

            p = g_slist_next(p);
        }
    }
}

/* refreshes a single cache value in a tree node */
static inline void
refresh_cache_value_L(GNode *node, CacheValue *cache)
{
    SwamiPropTreeNode *treenode, *obj_treenode;
    SwamiPropTreeValue *treeval;
    const char *prop_name;
    GType obj_type;
    GSList *p;
    int flags;

    obj_treenode = (SwamiPropTreeNode *)(node->data);
    obj_type = G_TYPE_FROM_INSTANCE(obj_treenode->object);
    prop_name = cache->pspec->name;

    do
    {
        /* loop over tree ancestry */
        treenode = (SwamiPropTreeNode *)(node->data);
        p = treenode->values;

        while(p)			/* loop over variables in each node */
        {
            treeval = (SwamiPropTreeValue *)(p->data);

            /* property type and name matches? */
            if((!treeval->prop_type || (treeval->prop_type == obj_type))
                    && strcmp(prop_name, treeval->prop_name) == 0)
            {
                /* property name matches */
                if(cache->value == treeval)
                {
                    return;    /* cache is correct? */
                }

                if(!cache->prop_ctrl)  /* no existing property control? */
                    cache->prop_ctrl = /* ++ ref new property control */
                        swami_get_control_prop_by_name(obj_treenode->object,
                                                       prop_name);
                else	     /* disconnect current property control */
                    swami_control_disconnect_all
                    ((SwamiControl *)(cache->prop_ctrl));

                /* update the cached tree value */
                cache->value = treeval;
                cache->value_node = treenode;

                flags = SWAMI_CONTROL_CONN_INIT;

                if(swami_control_get_flags(cache->prop_ctrl)
                        & SWAMI_CONTROL_SENDS)
                {
                    flags |= SWAMI_CONTROL_CONN_BIDIR;
                }

                /* connect tree value control to property control and initialize
                to current value (connect bi-direction if prop_ctrl sends) */
                swami_control_connect(treeval->control,
                                      (SwamiControl *)(cache->prop_ctrl),
                                      flags);
                return;
            }

            p = g_slist_next(p);
        }
    }
    while((node = node->parent));

    /* no tree value found to satisfy cache property criteria */

    if(cache->prop_ctrl)	/* destroy unused property control? */
    {
        swami_control_disconnect_all((SwamiControl *)(cache->prop_ctrl));
        g_object_unref(cache->prop_ctrl);  /* -- unref prop ctrl from cache */
        cache->prop_ctrl = NULL;
    }

    /* set cached value to "unset" state */
    cache->value = NULL;
    cache->value_node = NULL;
}
