/*
 *  Copyright © 2002 Jorn Baayen <jorn@nl.linux.org>
 *  Copyright © 2003 Marco Pesenti Gritti
 *  Copyright © 2003 Christian Persch
 *
 *  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; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <glib.h>
#include <glib/gi18n.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <gdk/gdk.h>
#include <time.h>

#include "ephy-node.h"

typedef struct {
  EphyNode *node;
  int id;
  EphyNodeCallback callback;
  EphyNodeSignalType type;
  gpointer data;
  gboolean invalidated;
} EphyNodeSignalData;

typedef struct {
  EphyNode *node;
  guint index;
} EphyNodeParent;

typedef struct {
  EphyNode *node;
  guint property_id;
} EphyNodeChange;

struct _EphyNode {
  int ref_count;

  guint id;

  GPtrArray *properties;

  GHashTable *parents;
  GPtrArray *children;

  GHashTable *signals;
  int signal_id;
  guint emissions;
  guint invalidated_signals;
  guint is_drag_source : 1;
  guint is_drag_dest : 1;

  EphyNodeDb *db;
};

typedef struct {
  EphyNodeSignalType type;
  va_list valist;
} ENESCData;

static gboolean
int_equal (gconstpointer a,
           gconstpointer b)
{
  return GPOINTER_TO_INT (a) == GPOINTER_TO_INT (b);
}

static guint
int_hash (gconstpointer a)
{
  return GPOINTER_TO_INT (a);
}

static void
callback (long id, EphyNodeSignalData *data, gpointer *dummy)
{
  ENESCData *user_data;
  va_list valist;

  if (data->invalidated) return;

  user_data = (ENESCData *)dummy;

  G_VA_COPY (valist, user_data->valist);

  if (data->type != user_data->type) return;

  switch (data->type) {
    case EPHY_NODE_DESTROY:
    case EPHY_NODE_RESTORED:
      data->callback (data->node, data->data);
      break;

    case EPHY_NODE_CHANGED:
    {
      guint property_id;

      property_id = va_arg (valist, guint);

      data->callback (data->node, property_id, data->data);
    }
    break;

    case EPHY_NODE_CHILD_ADDED:
    {
      EphyNode *node;

      node = va_arg (valist, EphyNode *);

      data->callback (data->node, node, data->data);
    }
    break;

    case EPHY_NODE_CHILD_CHANGED:
    {
      EphyNode *node;
      guint property_id;

      node = va_arg (valist, EphyNode *);
      property_id = va_arg (valist, guint);

      data->callback (data->node, node, property_id, data->data);
    }
    break;

    case EPHY_NODE_CHILD_REMOVED:
    {
      EphyNode *node;
      guint last_index;

      node = va_arg (valist, EphyNode *);
      last_index = va_arg (valist, guint);

      data->callback (data->node, node, last_index, data->data);
    }
    break;

    case EPHY_NODE_CHILDREN_REORDERED:
      data->callback (data->node, va_arg (valist, int *), data->data);
      break;

    default:
      break;
  }

  va_end (valist);
}

static gboolean
remove_invalidated_signals (long                id,
                            EphyNodeSignalData *data,
                            gpointer            user_data)
{
  return data->invalidated;
}

static void
ephy_node_emit_signal (EphyNode *node, EphyNodeSignalType type, ...)
{
  ENESCData data;

  ++node->emissions;

  va_start (data.valist, type);

  data.type = type;

  g_hash_table_foreach (node->signals,
                        (GHFunc)callback,
                        &data);

  va_end (data.valist);

  if (G_UNLIKELY (--node->emissions == 0 && node->invalidated_signals)) {
    guint removed;

    removed = g_hash_table_foreach_remove
                (node->signals,
                (GHRFunc)remove_invalidated_signals,
                NULL);
    g_assert (removed == node->invalidated_signals);

    node->invalidated_signals = 0;
  }
}

static inline void
real_remove_child (EphyNode *node,
                   EphyNode *child,
                   gboolean  remove_from_parent,
                   gboolean  remove_from_child)
{
  EphyNodeParent *node_info;

  node_info = g_hash_table_lookup (child->parents,
                                   GINT_TO_POINTER (node->id));

  if (remove_from_parent) {
    guint i;
    guint old_index;

    old_index = node_info->index;

    g_ptr_array_remove_index (node->children,
                              node_info->index);

    /* correct indices on kids */
    for (i = node_info->index; i < node->children->len; i++) {
      EphyNode *borked_node;
      EphyNodeParent *borked_node_info;

      borked_node = g_ptr_array_index (node->children, i);


      borked_node_info = g_hash_table_lookup (borked_node->parents,
                                              GINT_TO_POINTER (node->id));
      borked_node_info->index--;
    }

    ephy_node_emit_signal (node, EPHY_NODE_CHILD_REMOVED, child, old_index);
  }

  if (remove_from_child) {
    g_hash_table_remove (child->parents,
                         GINT_TO_POINTER (node->id));
  }
}

static void
remove_child (long            id,
              EphyNodeParent *node_info,
              EphyNode       *node)
{
  real_remove_child (node_info->node, node, TRUE, FALSE);
}

static void
signal_object_weak_notify (EphyNodeSignalData *signal_data,
                           GObject            *where_the_object_was)
{
  signal_data->data = NULL;
  ephy_node_signal_disconnect (signal_data->node, signal_data->id);
}

static void
destroy_signal_data (EphyNodeSignalData *signal_data)
{
  if (signal_data->data) {
    g_object_weak_unref (G_OBJECT (signal_data->data),
                         (GWeakNotify)signal_object_weak_notify,
                         signal_data);
  }

  g_slice_free (EphyNodeSignalData, signal_data);
}

static void
node_parent_free (EphyNodeParent *parent)
{
  g_slice_free (EphyNodeParent, parent);
}

static void
ephy_node_destroy (EphyNode *node)
{
  guint i;

  ephy_node_emit_signal (node, EPHY_NODE_DESTROY);

  /* Remove from parents. */
  g_hash_table_foreach (node->parents,
                        (GHFunc)remove_child,
                        node);
  g_hash_table_destroy (node->parents);

  /* Remove children. */
  for (i = 0; i < node->children->len; i++) {
    EphyNode *child;

    child = g_ptr_array_index (node->children, i);

    real_remove_child (node, child, FALSE, TRUE);
  }
  g_ptr_array_free (node->children, TRUE);

  /* Remove signals. */
  g_hash_table_destroy (node->signals);

  /* Remove id. */
  _ephy_node_db_remove_id (node->db, node->id);

  /* Remove properties. */
  for (i = 0; i < node->properties->len; i++) {
    GValue *val;

    val = g_ptr_array_index (node->properties, i);

    if (val != NULL) {
      g_value_unset (val);
      g_slice_free (GValue, val);
    }
  }
  g_ptr_array_free (node->properties, TRUE);

  g_slice_free (EphyNode, node);
}

EphyNode *
ephy_node_new (EphyNodeDb *db)
{
  long id;

  g_return_val_if_fail (EPHY_IS_NODE_DB (db), NULL);

  if (ephy_node_db_is_immutable (db)) return NULL;

  id = _ephy_node_db_new_id (db);

  return ephy_node_new_with_id (db, id);
}

EphyNode *
ephy_node_new_with_id (EphyNodeDb *db, guint reserved_id)
{
  EphyNode *node;

  g_return_val_if_fail (EPHY_IS_NODE_DB (db), NULL);

  if (ephy_node_db_is_immutable (db)) return NULL;

  node = g_slice_new0 (EphyNode);

  node->ref_count = 1;

  node->id = reserved_id;

  node->db = db;

  node->properties = g_ptr_array_new ();

  node->children = g_ptr_array_new ();

  node->parents = g_hash_table_new_full
                    (int_hash, int_equal, NULL, (GDestroyNotify)node_parent_free);

  node->signals = g_hash_table_new_full
                    (int_hash, int_equal, NULL,
                    (GDestroyNotify)destroy_signal_data);

  node->signal_id = 0;
  node->emissions = 0;
  node->invalidated_signals = 0;
  node->is_drag_source = TRUE;
  node->is_drag_dest = TRUE;

  _ephy_node_db_add_id (db, reserved_id, node);

  return node;
}

/**
 * ephy_node_get_db:
 *
 * Return value: (transfer none):
 **/
EphyNodeDb *
ephy_node_get_db (EphyNode *node)
{
  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);

  return node->db;
}

guint
ephy_node_get_id (EphyNode *node)
{
  long ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), G_MAXUINT);

  ret = node->id;

  return ret;
}

void
ephy_node_ref (EphyNode *node)
{
  g_return_if_fail (EPHY_IS_NODE (node));

  node->ref_count++;
}

void
ephy_node_unref (EphyNode *node)
{
  g_return_if_fail (EPHY_IS_NODE (node));

  node->ref_count--;

  if (node->ref_count <= 0) {
    ephy_node_destroy (node);
  }
}

static void
child_changed (guint           id,
               EphyNodeParent *node_info,
               EphyNodeChange *change)
{
  ephy_node_emit_signal (node_info->node, EPHY_NODE_CHILD_CHANGED,
                         change->node, change->property_id);
}

static inline void
real_set_property (EphyNode *node,
                   guint     property_id,
                   GValue   *value)
{
  GValue *old;

  if (property_id >= node->properties->len) {
    g_ptr_array_set_size (node->properties, property_id + 1);
  }

  old = g_ptr_array_index (node->properties, property_id);
  if (old != NULL) {
    g_value_unset (old);
    g_slice_free (GValue, old);
  }

  g_ptr_array_index (node->properties, property_id) = value;
}

static inline void
ephy_node_set_property_internal (EphyNode *node,
                                 guint     property_id,
                                 GValue   *value)
{
  EphyNodeChange change;

  real_set_property (node, property_id, value);

  change.node = node;
  change.property_id = property_id;
  g_hash_table_foreach (node->parents,
                        (GHFunc)child_changed,
                        &change);

  ephy_node_emit_signal (node, EPHY_NODE_CHANGED, property_id);
}

void
ephy_node_set_property (EphyNode     *node,
                        guint         property_id,
                        const GValue *value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));
  g_return_if_fail (value != NULL);

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_VALUE_TYPE (value));
  g_value_copy (value, new);

  ephy_node_set_property_internal (node, property_id, new);
}

/**
 * ephy_node_get_property:
 * @property_id: the identifier for the property
 * @value: (out): the variable to hold the value
 */
gboolean
ephy_node_get_property (EphyNode *node,
                        guint     property_id,
                        GValue   *value)
{
  GValue *ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);
  g_return_val_if_fail (value != NULL, FALSE);

  if (property_id >= node->properties->len) {
    return FALSE;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return FALSE;
  }

  g_value_init (value, G_VALUE_TYPE (ret));
  g_value_copy (ret, value);

  return TRUE;
}

void
ephy_node_set_property_string (EphyNode   *node,
                               guint       property_id,
                               const char *value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_STRING);
  g_value_set_string (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

const char *
ephy_node_get_property_string (EphyNode *node,
                               guint     property_id)
{
  GValue *ret;
  const char *retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);

  if (property_id >= node->properties->len) {
    return NULL;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return NULL;
  }

  retval = g_value_get_string (ret);

  return retval;
}

void
ephy_node_set_property_boolean (EphyNode *node,
                                guint     property_id,
                                gboolean  value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_BOOLEAN);
  g_value_set_boolean (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

gboolean
ephy_node_get_property_boolean (EphyNode *node,
                                guint     property_id)
{
  GValue *ret;
  gboolean retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);

  if (property_id >= node->properties->len) {
    return FALSE;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return FALSE;
  }

  retval = g_value_get_boolean (ret);

  return retval;
}

void
ephy_node_set_property_long (EphyNode *node,
                             guint     property_id,
                             long      value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_LONG);
  g_value_set_long (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

long
ephy_node_get_property_long (EphyNode *node,
                             guint     property_id)
{
  GValue *ret;
  long retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);

  if (property_id >= node->properties->len) {
    return -1;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return -1;
  }

  retval = g_value_get_long (ret);

  return retval;
}

void
ephy_node_set_property_int (EphyNode *node,
                            guint     property_id,
                            int       value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_INT);
  g_value_set_int (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

int
ephy_node_get_property_int (EphyNode *node,
                            guint     property_id)
{
  GValue *ret;
  int retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);

  if (property_id >= node->properties->len) {
    return -1;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return -1;
  }

  retval = g_value_get_int (ret);

  return retval;
}

void
ephy_node_set_property_double (EphyNode *node,
                               guint     property_id,
                               double    value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_DOUBLE);
  g_value_set_double (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

double
ephy_node_get_property_double (EphyNode *node,
                               guint     property_id)
{
  GValue *ret;
  double retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);

  if (property_id >= node->properties->len) {
    return -1;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return -1;
  }

  retval = g_value_get_double (ret);

  return retval;
}

void
ephy_node_set_property_float (EphyNode *node,
                              guint     property_id,
                              float     value)
{
  GValue *new;

  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  new = g_slice_new0 (GValue);
  g_value_init (new, G_TYPE_FLOAT);
  g_value_set_float (new, value);

  ephy_node_set_property_internal (node, property_id, new);
}

float
ephy_node_get_property_float (EphyNode *node,
                              guint     property_id)
{
  GValue *ret;
  float retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);

  if (property_id >= node->properties->len) {
    return -1;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return -1;
  }

  retval = g_value_get_float (ret);

  return retval;
}

/**
 * ephy_node_get_property_node:
 *
 * Return value: (transfer none):
 **/
EphyNode *
ephy_node_get_property_node (EphyNode *node,
                             guint     property_id)
{
  GValue *ret;
  EphyNode *retval;

  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);

  if (property_id >= node->properties->len) {
    return NULL;
  }

  ret = g_ptr_array_index (node->properties, property_id);
  if (ret == NULL) {
    return NULL;
  }

  retval = g_value_get_pointer (ret);

  return retval;
}

typedef struct {
  xmlTextWriterPtr writer;
  int ret;
} ForEachData;

static void
write_parent (guint           id,
              EphyNodeParent *node_info,
              ForEachData    *data)
{
  xmlTextWriterPtr writer = data->writer;

  /* there already was an error, do nothing. this works around
   * the fact that g_hash_table_foreach cannot be cancelled.
   */
  if (data->ret < 0) return;

  data->ret = xmlTextWriterStartElement (writer, (const xmlChar *)"parent");
  if (data->ret < 0) return;

  data->ret = xmlTextWriterWriteFormatAttribute
                (writer, (const xmlChar *)"id", "%d", node_info->node->id);
  if (data->ret < 0) return;

  data->ret = xmlTextWriterEndElement (writer);       /* parent */
  if (data->ret < 0) return;
}

static inline int
safe_write_string (xmlTextWriterPtr writer,
                   const xmlChar   *string)
{
  int ret;
  xmlChar *copy, *p;

  if (!string)
    return 0;

  /* http://www.w3.org/TR/REC-xml/#sec-well-formed :
     Character Range
     [2]     Char       ::=          #x9 | #xA | #xD | [#x20-#xD7FF] |
     [#xE000-#xFFFD] | [#x10000-#x10FFFF]
     any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
   */

  copy = xmlStrdup (string);
  for (p = copy; *p; p++) {
    xmlChar c = *p;
    if (G_UNLIKELY (c < 0x20 && c != 0xd && c != 0xa && c != 0x9)) {
      *p = 0x20;
    }
  }

  ret = xmlTextWriterWriteString (writer, copy);
  xmlFree (copy);

  return ret;
}

int
ephy_node_write_to_xml (EphyNode        *node,
                        xmlTextWriterPtr writer)
{
  xmlChar xml_buf[G_ASCII_DTOSTR_BUF_SIZE];
  guint i;
  int ret;
  ForEachData data;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);
  g_return_val_if_fail (writer != NULL, -1);

  /* start writing the node */
  ret = xmlTextWriterStartElement (writer, (const xmlChar *)"node");
  if (ret < 0) goto out;

  /* write node id */
  ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *)"id", "%d", node->id);
  if (ret < 0) goto out;

  /* write node properties */
  for (i = 0; i < node->properties->len; i++) {
    GValue *value;

    value = g_ptr_array_index (node->properties, i);

    if (value == NULL) continue;
    if (G_VALUE_TYPE (value) == G_TYPE_STRING &&
        g_value_get_string (value) == NULL) continue;

    ret = xmlTextWriterStartElement (writer, (const xmlChar *)"property");
    if (ret < 0) break;

    ret = xmlTextWriterWriteFormatAttribute (writer, (const xmlChar *)"id", "%d", i);
    if (ret < 0) break;

    ret = xmlTextWriterWriteAttribute
            (writer, (const xmlChar *)"value_type",
            (const xmlChar *)g_type_name (G_VALUE_TYPE (value)));
    if (ret < 0) break;

    switch (G_VALUE_TYPE (value)) {
      case G_TYPE_STRING:
        ret = safe_write_string
                (writer, (const xmlChar *)g_value_get_string (value));
        break;
      case G_TYPE_BOOLEAN:
        ret = xmlTextWriterWriteFormatString
                (writer, "%d", g_value_get_boolean (value));
        break;
      case G_TYPE_INT:
        ret = xmlTextWriterWriteFormatString
                (writer, "%d", g_value_get_int (value));
        break;
      case G_TYPE_LONG:
        ret = xmlTextWriterWriteFormatString
                (writer, "%ld", g_value_get_long (value));
        break;
      case G_TYPE_FLOAT:
        g_ascii_dtostr ((gchar *)xml_buf, sizeof (xml_buf),
                        g_value_get_float (value));
        ret = xmlTextWriterWriteString (writer, xml_buf);
        break;
      case G_TYPE_DOUBLE:
        g_ascii_dtostr ((gchar *)xml_buf, sizeof (xml_buf),
                        g_value_get_double (value));
        ret = xmlTextWriterWriteString (writer, xml_buf);
        break;
      default:
        g_assert_not_reached ();
        break;
    }
    if (ret < 0) break;

    ret = xmlTextWriterEndElement (writer);             /* property */
    if (ret < 0) break;
  }
  if (ret < 0) goto out;

  /* now write parent node ids */
  data.writer = writer;
  data.ret = 0;

  g_hash_table_foreach (node->parents,
                        (GHFunc)write_parent,
                        &data);
  ret = data.ret;
  if (ret < 0) goto out;

  ret = xmlTextWriterEndElement (writer);       /* node */
  if (ret < 0) goto out;

 out:
  return ret >= 0 ? 0 : -1;
}

static inline void
real_add_child (EphyNode *node,
                EphyNode *child)
{
  EphyNodeParent *node_info;

  if (g_hash_table_lookup (child->parents,
                           GINT_TO_POINTER (node->id)) != NULL) {
    return;
  }

  g_ptr_array_add (node->children, child);

  node_info = g_slice_new0 (EphyNodeParent);
  node_info->node = node;
  node_info->index = node->children->len - 1;

  g_hash_table_insert (child->parents,
                       GINT_TO_POINTER (node->id),
                       node_info);
}

EphyNode *
ephy_node_new_from_xml (EphyNodeDb *db, xmlNodePtr xml_node)
{
  EphyNode *node;
  xmlNodePtr xml_child;
  xmlChar *xml;
  long id;

  g_return_val_if_fail (EPHY_IS_NODE_DB (db), NULL);
  g_return_val_if_fail (xml_node != NULL, NULL);

  if (ephy_node_db_is_immutable (db)) return NULL;

  xml = xmlGetProp (xml_node, (const xmlChar *)"id");
  if (xml == NULL)
    return NULL;
  id = atol ((const char *)xml);
  xmlFree (xml);

  node = ephy_node_new_with_id (db, id);

  for (xml_child = xml_node->children; xml_child != NULL; xml_child = xml_child->next) {
    if (strcmp ((const char *)xml_child->name, "parent") == 0) {
      EphyNode *parent;
      long parent_id;

      xml = xmlGetProp (xml_child, (const xmlChar *)"id");
      g_assert (xml != NULL);
      parent_id = atol ((const char *)xml);
      xmlFree (xml);

      parent = ephy_node_db_get_node_from_id (db, parent_id);

      if (parent != NULL) {
        real_add_child (parent, node);

        ephy_node_emit_signal (parent, EPHY_NODE_CHILD_ADDED, node);
      }
    } else if (strcmp ((const char *)xml_child->name, "property") == 0) {
      GValue *value;
      xmlChar *xmlType, *xmlValue;
      int property_id;

      xml = xmlGetProp (xml_child, (const xmlChar *)"id");
      property_id = atoi ((const char *)xml);
      xmlFree (xml);

      xmlType = xmlGetProp (xml_child, (const xmlChar *)"value_type");
      xmlValue = xmlNodeGetContent (xml_child);

      value = g_slice_new0 (GValue);

      if (xmlStrEqual (xmlType, (const xmlChar *)"gchararray")) {
        g_value_init (value, G_TYPE_STRING);
        g_value_set_string (value, (const gchar *)xmlValue);
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"gint")) {
        g_value_init (value, G_TYPE_INT);
        g_value_set_int (value, atoi ((const char *)xmlValue));
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"gboolean")) {
        g_value_init (value, G_TYPE_BOOLEAN);
        g_value_set_boolean (value, atoi ((const char *)xmlValue));
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"glong")) {
        g_value_init (value, G_TYPE_LONG);
        g_value_set_long (value, atol ((const char *)xmlValue));
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"gfloat")) {
        g_value_init (value, G_TYPE_FLOAT);
        g_value_set_float (value, g_ascii_strtod ((const gchar *)xmlValue, NULL));
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"gdouble")) {
        g_value_init (value, G_TYPE_DOUBLE);
        g_value_set_double (value, g_ascii_strtod ((const gchar *)xmlValue, NULL));
      } else if (xmlStrEqual (xmlType, (const xmlChar *)"gpointer")) {
        EphyNode *property_node;

        property_node = ephy_node_db_get_node_from_id (db, atol ((const char *)xmlValue));

        g_value_set_pointer (value, property_node);
        break;
      } else {
        g_assert_not_reached ();
      }

      real_set_property (node, property_id, value);

      xmlFree (xmlValue);
      xmlFree (xmlType);
    }
  }

  ephy_node_emit_signal (node, EPHY_NODE_RESTORED);

  return node;
}

void
ephy_node_add_child (EphyNode *node,
                     EphyNode *child)
{
  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  real_add_child (node, child);

  ephy_node_emit_signal (node, EPHY_NODE_CHILD_ADDED, child);
}

void
ephy_node_remove_child (EphyNode *node,
                        EphyNode *child)
{
  g_return_if_fail (EPHY_IS_NODE (node));

  if (ephy_node_db_is_immutable (node->db)) return;

  real_remove_child (node, child, TRUE, TRUE);
}

gboolean
ephy_node_has_child (EphyNode *node,
                     EphyNode *child)
{
  gboolean ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), FALSE);

  ret = (g_hash_table_lookup (child->parents,
                              GINT_TO_POINTER (node->id)) != NULL);

  return ret;
}

static int
ephy_node_real_get_child_index (EphyNode *node,
                                EphyNode *child)
{
  EphyNodeParent *node_info;
  int ret;

  node_info = g_hash_table_lookup (child->parents,
                                   GINT_TO_POINTER (node->id));

  if (node_info == NULL)
    return -1;

  ret = node_info->index;

  return ret;
}

/**
 * ephy_node_sort_children:
 * @node: an #EphyNode
 * @compare_func: (scope call): function to compare children
 *
 * Sorts the children of @node using @compare_func.
 *
 **/
void
ephy_node_sort_children (EphyNode    *node,
                         GCompareFunc compare_func)
{
  GPtrArray *newkids;
  guint i, *new_order;

  if (ephy_node_db_is_immutable (node->db)) return;

  g_return_if_fail (EPHY_IS_NODE (node));
  g_return_if_fail (compare_func != NULL);

  newkids = g_ptr_array_new ();
  g_ptr_array_set_size (newkids, node->children->len);

  /* dup the array */
  for (i = 0; i < node->children->len; i++) {
    g_ptr_array_index (newkids, i) = g_ptr_array_index (node->children, i);
  }

  g_ptr_array_sort (newkids, compare_func);

  new_order = g_new (guint, newkids->len);
  memset (new_order, -1, sizeof (guint) * newkids->len);

  for (i = 0; i < newkids->len; i++) {
    EphyNodeParent *node_info;
    EphyNode *child;

    child = g_ptr_array_index (newkids, i);
    new_order[ephy_node_real_get_child_index (node, child)] = i;
    node_info = g_hash_table_lookup (child->parents,
                                     GINT_TO_POINTER (node->id));
    node_info->index = i;
  }

  g_ptr_array_free (node->children, FALSE);
  node->children = newkids;

  ephy_node_emit_signal (node, EPHY_NODE_CHILDREN_REORDERED, new_order);

  g_free (new_order);
}

void
ephy_node_reorder_children (EphyNode *node,
                            int      *new_order)
{
  GPtrArray *newkids;
  guint i;

  g_return_if_fail (EPHY_IS_NODE (node));
  g_return_if_fail (new_order != NULL);

  if (ephy_node_db_is_immutable (node->db)) return;

  newkids = g_ptr_array_new ();
  g_ptr_array_set_size (newkids, node->children->len);

  for (i = 0; i < node->children->len; i++) {
    EphyNode *child;
    EphyNodeParent *node_info;

    child = g_ptr_array_index (node->children, i);

    g_ptr_array_index (newkids, new_order[i]) = child;

    node_info = g_hash_table_lookup (child->parents,
                                     GINT_TO_POINTER (node->id));
    node_info->index = new_order[i];
  }

  g_ptr_array_free (node->children, FALSE);
  node->children = newkids;

  ephy_node_emit_signal (node, EPHY_NODE_CHILDREN_REORDERED, new_order);
}

/**
 * ephy_node_get_children:
 *
 * Return value: (array) (element-type EphyNode) (transfer none):
 **/
GPtrArray *
ephy_node_get_children (EphyNode *node)
{
  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);

  return node->children;
}

int
ephy_node_get_n_children (EphyNode *node)
{
  int ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);

  ret = node->children->len;

  return ret;
}

/**
 * ephy_node_get_nth_child:
 *
 * Return value: (transfer none):
 **/
EphyNode *
ephy_node_get_nth_child (EphyNode *node,
                         guint     n)
{
  EphyNode *ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);

  if (n < node->children->len) {
    ret = g_ptr_array_index (node->children, n);
  } else {
    ret = NULL;
  }

  return ret;
}

static inline int
get_child_index_real (EphyNode *node,
                      EphyNode *child)
{
  EphyNodeParent *node_info;

  node_info = g_hash_table_lookup (child->parents,
                                   GINT_TO_POINTER (node->id));

  if (node_info == NULL)
    return -1;

  return node_info->index;
}


int
ephy_node_get_child_index (EphyNode *node,
                           EphyNode *child)
{
  int ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);
  g_return_val_if_fail (EPHY_IS_NODE (child), -1);

  ret = ephy_node_real_get_child_index (node, child);

  return ret;
}

/**
 * ephy_node_get_next_child:
 *
 * Return value: (transfer none):
 **/
EphyNode *
ephy_node_get_next_child (EphyNode *node,
                          EphyNode *child)
{
  EphyNode *ret;
  guint idx;

  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
  g_return_val_if_fail (EPHY_IS_NODE (child), NULL);

  idx = get_child_index_real (node, child);

  if ((idx + 1) < node->children->len) {
    ret = g_ptr_array_index (node->children, idx + 1);
  } else {
    ret = NULL;
  }

  return ret;
}

/**
 * ephy_node_get_previous_child:
 *
 * Return value: (transfer none):
 **/
EphyNode *
ephy_node_get_previous_child (EphyNode *node,
                              EphyNode *child)
{
  EphyNode *ret;
  int idx;

  g_return_val_if_fail (EPHY_IS_NODE (node), NULL);
  g_return_val_if_fail (EPHY_IS_NODE (child), NULL);

  idx = get_child_index_real (node, child);

  if ((idx - 1) >= 0) {
    ret = g_ptr_array_index (node->children, idx - 1);
  } else {
    ret = NULL;
  }

  return ret;
}

/**
 * ephy_node_signal_connect_object:
 * @node: an #EphyNode
 * @type: signal type
 * @cb: (scope notified): the callback to connect
 * @object: data to pass to @cb
 *
 * Connects a callback function to the @type signal of @node.
 *
 * Returns: an identifier for the connected signal
 **/
int
ephy_node_signal_connect_object (EphyNode          *node,
                                 EphyNodeSignalType type,
                                 EphyNodeCallback   cb,
                                 GObject           *object)
{
  EphyNodeSignalData *signal_data;
  int ret;

  g_return_val_if_fail (EPHY_IS_NODE (node), -1);
  /* FIXME: */
  g_return_val_if_fail (node->emissions == 0, -1);

  signal_data = g_slice_new0 (EphyNodeSignalData);
  signal_data->node = node;
  signal_data->id = node->signal_id;
  signal_data->callback = cb;
  signal_data->type = type;
  signal_data->data = object;

  g_hash_table_insert (node->signals,
                       GINT_TO_POINTER (node->signal_id),
                       signal_data);
  if (object) {
    g_object_weak_ref (object,
                       (GWeakNotify)signal_object_weak_notify,
                       signal_data);
  }

  ret = node->signal_id;
  node->signal_id++;

  return ret;
}

static gboolean
remove_matching_signal_data (gpointer            key,
                             EphyNodeSignalData *signal_data,
                             EphyNodeSignalData *user_data)
{
  return (user_data->data == signal_data->data &&
          user_data->type == signal_data->type &&
          user_data->callback == signal_data->callback);
}

static void
invalidate_matching_signal_data (gpointer            key,
                                 EphyNodeSignalData *signal_data,
                                 EphyNodeSignalData *user_data)
{
  if (user_data->data == signal_data->data &&
      user_data->type == signal_data->type &&
      user_data->callback == signal_data->callback &&
      !signal_data->invalidated) {
    signal_data->invalidated = TRUE;
    ++signal_data->node->invalidated_signals;
  }
}

/**
 * ephy_node_signal_disconnect_object:
 * @node: an #EphyNode
 * @type: signal type
 * @cb: (scope notified): the callback to disconnect
 * @object: data passed to @cb when it was connected
 *
 * Disconnects @cb from @type in @node. @cb is identified by the
 * @object previously passed in ephy_node_signal_connect_object.
 *
 * Returns: the number of signal handlers removed
 **/
guint
ephy_node_signal_disconnect_object (EphyNode          *node,
                                    EphyNodeSignalType type,
                                    EphyNodeCallback   cb,
                                    GObject           *object)
{
  EphyNodeSignalData user_data;

  g_return_val_if_fail (EPHY_IS_NODE (node), 0);

  user_data.callback = cb;
  user_data.type = type;
  user_data.data = object;

  if (G_LIKELY (node->emissions == 0)) {
    return g_hash_table_foreach_remove (node->signals,
                                        (GHRFunc)remove_matching_signal_data,
                                        &user_data);
  } else {
    g_hash_table_foreach (node->signals,
                          (GHFunc)invalidate_matching_signal_data,
                          &user_data);
    return 0;
  }
}

void
ephy_node_signal_disconnect (EphyNode *node,
                             int       signal_id)
{
  g_return_if_fail (EPHY_IS_NODE (node));
  g_return_if_fail (signal_id != -1);

  if (G_LIKELY (node->emissions == 0)) {
    g_hash_table_remove (node->signals,
                         GINT_TO_POINTER (signal_id));
  } else {
    EphyNodeSignalData *data;

    data = g_hash_table_lookup (node->signals,
                                GINT_TO_POINTER (signal_id));
    g_return_if_fail (data != NULL);
    g_return_if_fail (!data->invalidated);

    data->invalidated = TRUE;
    node->invalidated_signals++;
  }
}

void
ephy_node_set_is_drag_source (EphyNode *node,
                              gboolean  allow)
{
  node->is_drag_source = allow != FALSE;
}

gboolean
ephy_node_get_is_drag_source (EphyNode *node)
{
  return node->is_drag_source;
}

void
ephy_node_set_is_drag_dest (EphyNode *node,
                            gboolean  allow)
{
  node->is_drag_dest = allow != FALSE;
}

gboolean
ephy_node_get_is_drag_dest (EphyNode *node)
{
  return node->is_drag_dest;
}

GType
ephy_node_get_type (void)
{
  static GType type = 0;

  if (G_UNLIKELY (type == 0)) {
    type = g_boxed_type_register_static ("EphyNode",
                                         (GBoxedCopyFunc)ephy_node_ref,
                                         (GBoxedFreeFunc)ephy_node_unref);
  }

  return type;
}
