#include "gskxml.h"
#include "../gskqsortmacro.h"
#include <string.h>

static GHashTable *ns_table = NULL;

#define XML_NODE_MAGIC          0x33ae

G_LOCK_DEFINE_STATIC (ns_table);

static guint
ns_hash (gconstpointer a)
{
  const GskXmlNamespace *ns = a;
  gsize ns_abbrev = GPOINTER_TO_SIZE (ns->abbrev);
  gsize ns_uri = GPOINTER_TO_SIZE (ns->uri);
  return ns_uri ^ (ns_abbrev << 4);
}

static gboolean
ns_equal (gconstpointer a, gconstpointer b)
{
  const GskXmlNamespace *ns_a = a;
  const GskXmlNamespace *ns_b = b;
  return ns_a->abbrev == ns_b->abbrev
      && ns_a->uri == ns_b->uri;
}

GskXmlNamespace *
gsk_xml_namespace_new   (GskXmlString *abbrev, /* may be NULL */
                         GskXmlString *uri)
{
  GskXmlNamespace dummy;
  GskXmlNamespace *rv;
  dummy.abbrev = abbrev;
  dummy.uri = uri;
  G_LOCK (ns_table);
  if (ns_table == NULL)
    ns_table = g_hash_table_new (ns_hash, ns_equal);
  rv = g_hash_table_lookup (ns_table, &dummy);
  if (rv)
    {
      ++(rv->ref_count);
      G_UNLOCK (ns_table);
      return rv;
    }
  else
    {
      rv = g_new (GskXmlNamespace, 1);
      rv->ref_count = 1;
      rv->abbrev = abbrev ? gsk_xml_string_ref (abbrev) : NULL;
      rv->uri = gsk_xml_string_ref (uri);
      g_hash_table_insert (ns_table, rv, rv);
      G_UNLOCK (ns_table);
      return rv;
    }
}

GskXmlNamespace *gsk_xml_namespace_ref   (GskXmlNamespace *ns)
{
  G_LOCK (ns_table);
  ++(ns->ref_count);
  G_UNLOCK (ns_table);
  return ns;
}

void             gsk_xml_namespace_unref (GskXmlNamespace *ns)
{
  G_LOCK (ns_table);
  if (--(ns->ref_count) == 0)
    {
      g_hash_table_remove (ns_table, ns);
      g_free (ns);
    }
  G_UNLOCK (ns_table);
}

GskXmlNode *
gsk_xml_node_new_text    (GskXmlString *text)
{
  GskXmlNode *node = g_new (GskXmlNode, 1);
  node->base.ref_count = 1;
  node->base.magic = XML_NODE_MAGIC;
  node->base.type = GSK_XML_NODE_TYPE_TEXT;
  node->v_text.content = gsk_xml_string_ref (text);
  return node;
}

GskXmlNode *
gsk_xml_node_new_text_c  (const char *str)
{
  GskXmlNode *node = g_new (GskXmlNode, 1);
  node->base.ref_count = 1;
  node->base.magic = XML_NODE_MAGIC;
  node->base.type = GSK_XML_NODE_TYPE_TEXT;
  node->v_text.content = gsk_xml_string_new (str);
  return node;
}


GskXmlNode *
gsk_xml_node_new_element (GskXmlNamespace *ns,
                          GskXmlString *name,
                          guint         n_attrs,
                          GskXmlAttribute*attrs,
                          guint         n_children,
                          GskXmlNode  **children)
{
  guint size = sizeof (GskXmlNodeElement)
             + sizeof (GskXmlAttribute) * n_attrs
             + sizeof (GskXmlNode *) * n_children;

  GskXmlNodeElement *rv;
  guint i;
  guint n_element_children = 0;
  guint8 *at;
  for (i = 0; i < n_children; i++)
    if (children[i]->type == GSK_XML_NODE_TYPE_ELEMENT)
      n_element_children++;
  size += n_element_children * sizeof (GskXmlNode *);

  rv = g_malloc (size);
  rv->base.ref_count = 1;
  rv->base.magic = XML_NODE_MAGIC;
  rv->base.type = GSK_XML_NODE_TYPE_ELEMENT;
  rv->ns = ns ? gsk_xml_namespace_ref (ns) : NULL;
  rv->name = gsk_xml_string_ref (name);
  at = (guint8 *) (rv + 1);
  if (n_attrs)
    {
      rv->attributes = (GskXmlAttribute *) at;
      memcpy (rv->attributes, attrs, sizeof (GskXmlAttribute) * n_attrs);
      for (i = 0; i < n_attrs; i++)
        {
          if (attrs[i].ns != NULL)
            gsk_xml_namespace_ref (attrs[i].ns);
          gsk_xml_string_ref (attrs[i].name);
          gsk_xml_string_ref (attrs[i].value);
        }
#define COMPARE_ATTRIBUTE(a,b,rv)            \
      if (a.ns == NULL)                      \
        {                                    \
          if (b.ns != NULL)                  \
            rv = -1;                         \
          else                               \
            rv = 0;                          \
        }                                    \
      else if (b.ns == NULL)                 \
        rv = 1;                              \
      else if (a.ns->uri < b.ns->uri)        \
        rv = -1;                             \
      else if (a.ns->uri > b.ns->uri)        \
        rv = 1;                              \
      else                                   \
        rv = 0;                              \
      if (rv == 0)                           \
        {                                    \
          if (a.name < b.name)               \
            rv = -1;                         \
          else if (a.name > b.name)          \
            rv = 1;                          \
        }
      GSK_QSORT (rv->attributes, GskXmlAttribute, n_attrs, COMPARE_ATTRIBUTE);
#undef COMPARE_ATTRIBUTE
      at = (guint8*) (rv->attributes + n_attrs);
    }
  else
    rv->attributes = NULL;
  rv->n_attributes = n_attrs;

  rv->n_children = n_children;
  rv->children = (GskXmlNode **) at;
  rv->n_element_children = n_element_children;
  rv->element_children = rv->children + n_children;
  n_element_children = 0;
  for (i = 0; i < n_children; i++)
    {
      rv->children[i] = gsk_xml_node_ref (children[i]);
      if (rv->children[i]->type == GSK_XML_NODE_TYPE_ELEMENT)
        rv->element_children[n_element_children++] = GUINT_TO_POINTER (i);
    }
#define COMPARE_CHILD_BY_INDEX(a,b,rv)                  \
    {                                                   \
      guint i_a = GPOINTER_TO_UINT (a);                 \
      guint i_b = GPOINTER_TO_UINT (b);                 \
      GskXmlNodeElement *child_a = &children[i_a]->v_element; \
      GskXmlNodeElement *child_b = &children[i_b]->v_element; \
      if (child_a->ns == NULL)                          \
        {                                               \
          if (child_b->ns == NULL)                      \
            rv = 0;                                     \
          else                                          \
            rv = -1;                                    \
        }                                               \
      else if (child_b->ns == NULL)                     \
        rv = 1;                                         \
      else if (child_a->ns->uri < child_b->ns->uri)     \
        rv = -1;                                        \
      else if (child_a->ns->uri > child_b->ns->uri)     \
        rv = 1;                                         \
      else                                              \
        rv = 0;                                         \
      if (rv == 0)                                      \
        {                                               \
          if (child_a->name < child_b->name)            \
            rv = -1;                                    \
          else if (child_a->name > child_b->name)       \
            rv = 1;                                     \
        }                                               \
      if (rv == 0)                                      \
        {                                               \
          if (i_a < i_b)                                \
            rv = -1;                                    \
          else if (i_a > i_b)                           \
            rv = 1;                                     \
        }                                               \
    }
  GSK_QSORT (rv->element_children,
             GskXmlNode *,
             n_element_children,
             COMPARE_CHILD_BY_INDEX);
#undef COMPARE_CHILD_BY_INDEX

  for (i = 0; i < n_element_children; i++)
    rv->element_children[i] = rv->children[GPOINTER_TO_UINT (rv->element_children[i])];

  return (GskXmlNode *) rv;
}

GskXmlNode *
gsk_xml_node_new_from_element_with_new_children
                         (GskXmlNode   *node,
                          guint         n_children,
                          GskXmlNode  **children)
{
  g_return_val_if_fail (node->type == GSK_XML_NODE_TYPE_ELEMENT, NULL);

  return gsk_xml_node_new_element (node->v_element.ns,
                                   node->v_element.name,
                                   node->v_element.n_attributes,
                                   node->v_element.attributes,
                                   n_children, children);
}

GskXmlNode *
gsk_xml_node_ref    (GskXmlNode   *node)
{
  g_assert (node->base.ref_count > 0);
  ++(node->base.ref_count);
  return node;
}

void
gsk_xml_node_unref  (GskXmlNode   *node)
{
  guint i;
  g_assert (node->base.ref_count > 0);
  --(node->base.ref_count);
  if (node->base.ref_count == 0)
    {
      switch (node->type)
        {
        case GSK_XML_NODE_TYPE_TEXT:
          gsk_xml_string_unref (node->v_text.content);
          break;

        case GSK_XML_NODE_TYPE_ELEMENT:
          for (i = 0; i < node->v_element.n_attributes; i++)
            {
              gsk_xml_string_unref (node->v_element.attributes[i].name);
              gsk_xml_string_unref (node->v_element.attributes[i].value);
              if (node->v_element.attributes[i].ns)
                gsk_xml_namespace_unref (node->v_element.attributes[i].ns);
            }
          for (i = 0; i < node->v_element.n_children; i++)
            gsk_xml_node_unref (node->v_element.children[i]);
          gsk_xml_string_unref (node->v_element.name);
          break;

        default:
          g_assert_not_reached ();
          break;
        }

      g_free (node);
    }
}

#define MAX_CHILDREN_FOR_STACK          32      /* somewhat conservative */

GskXmlNode *
gsk_xml_node_find_child  (GskXmlNode   *node,
                          GskXmlString *ns_uri,
                          GskXmlString *child_name,
                          guint         instance)
{
  guint lo, n;
  if (node->type != GSK_XML_NODE_TYPE_ELEMENT)
    return NULL;
  lo = 0;
  n = node->v_element.n_element_children;
#define MATCHES_RAW(test, rv)                                   \
  G_STMT_START{                                                 \
    if (test->v_element.ns == NULL)                             \
      rv = (ns_uri == NULL ? 0 : -1);                           \
    else if (test->v_element.ns->uri < ns_uri)                  \
      rv = -1;                                                  \
    else if (test->v_element.ns->uri > ns_uri)                  \
      rv = 1;                                                   \
    else                                                        \
      rv = 0;                                                   \
    if (rv == 0)                                                \
      {                                                         \
        if (test->v_element.name < child_name)                  \
          rv = -1;                                              \
        else if (test->v_element.name > child_name)             \
          rv = 1;                                               \
      }                                                         \
  }G_STMT_END
    
  while (n)
    {
      guint mid = lo + n / 2;
      GskXmlNode *mid_child = node->v_element.element_children[mid];
      /* is mid_child <=> ns_uri.child_name ? */
      int cmp;
      MATCHES_RAW (mid_child, cmp);
      if (cmp == 0)
        {
          if (mid == 0)
            goto found_first;
          else
            {
              GskXmlNode *t2 = node->v_element.element_children[mid-1];
              MATCHES_RAW (t2, cmp);
              if (cmp == 0)
                n /= 2;
              else
                {
                  lo = mid;
                  goto found_first;
                }
            }
        }
      else if (cmp < 0)
        {
          guint new_lo = mid + 1;
          n -= (new_lo - lo);
          lo = new_lo;
        }
      else
        n /= 2;
    }
  return NULL;

found_first:
  if (instance == 0)
    return node->v_element.element_children[lo];
  if (lo + instance >= node->v_element.n_element_children)
    return NULL;
  {
    GskXmlNode *rv = node->v_element.element_children[lo + instance];
    int cmp;
    MATCHES_RAW (rv, cmp);
    if (cmp == 0)
      return rv;
    else
      return NULL;
  }
}

typedef struct
{
  GskXmlString *first;
  GPtrArray *all;
} GetContentInfo;

static void
get_content_recursive (GetContentInfo *info,
                       GskXmlNode     *node)
{
  if (node->type == GSK_XML_NODE_TYPE_TEXT)
    {
      GskXmlString *str = node->v_text.content;
      if (info->first == NULL)
        info->first = str;
      else if (info->all == NULL)
        {
          info->all = g_ptr_array_new ();
          g_ptr_array_add (info->all, info->first);
          g_ptr_array_add (info->all, str);
        }
      else
        g_ptr_array_add (info->all, str);
    }
  else
    {
      guint i;
      for (i = 0; i < node->v_element.n_children; i++)
        get_content_recursive (info, node->v_element.children[i]);
    }
}

GskXmlString *gsk_xml_node_get_content (GskXmlNode *node)
{
  GetContentInfo info = {NULL, NULL};
  get_content_recursive (&info, node);
  if (info.first == NULL)
    return gsk_xml_string_new ("");
  else if (info.all == NULL)
    return gsk_xml_string_ref (info.first);
  else
    {
      GskXmlString *rv = gsk_xml_strings_concat (info.all->len, (GskXmlString**)(info.all->pdata));
      g_ptr_array_free (info.all, TRUE);
      return rv;
    }
}

gpointer gsk_xml_node_cast_check (gpointer node, GskXmlNodeType node_type)
{
  g_return_val_if_fail (((GskXmlNode*)node)->type == node_type, node);
  g_return_val_if_fail (((GskXmlNode*)node)->base.magic == XML_NODE_MAGIC, node);
  return node;
}
gpointer gsk_xml_node_cast_check_any_type (gpointer node)
{
  g_return_val_if_fail (((GskXmlNode*)node)->base.magic == XML_NODE_MAGIC, node);
  return node;
}


syntax highlighted by Code2HTML, v. 0.9.1