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

typedef struct _Preamble Preamble;
struct _Preamble
{
  guint ref_count;
  guint hash;
  Preamble *next_in_bin;
};

static inline GskXmlString *
PREAMBLE_TO_STRING (const Preamble *preamble)
{
  return (GskXmlString *) (preamble + 1);
}
#define PREAMBLE_TO_STR(preamble) GSK_XML_STR(PREAMBLE_TO_STRING(preamble))

#define XML_STRING_GET_PREAMBLE(xmlstr)  (((Preamble *) (xmlstr))-1)

static guint n_strs = 0;
static guint max_strs = 0;
static guint n_bins_log2;
static Preamble **bins;

#define OCCUPANCY_RATIO         3

GskXmlString *gsk_xml_string__xmlns;
GskXmlString *gsk_xml_string__;

void gsk_xml_string_init (void)
{
  if (bins == NULL)
    {
      n_bins_log2 = 10;
      bins = g_new0 (Preamble *, 1 << n_bins_log2);
      max_strs = (1 << n_bins_log2) / OCCUPANCY_RATIO;

      gsk_xml_string__xmlns = gsk_xml_string_new ("xmlns");
      gsk_xml_string__ = gsk_xml_string_new ("");
    }
}
static guint
my_hash (const char *str)
{
  guint rv = 5003;
  while (*str)
    {
      rv += (rv << 5);
      rv += (guint8) *str++;
    }
  return rv;
}
static guint
my_hash_len (const char *str, guint len)
{
  guint rv = 5003;
  while (len--)
    {
      rv += (rv << 5);
      rv += (guint8) *str++;
    }
  return rv;
}
static guint
my_hash_strarray (guint n_strs, GskXmlString **strs)
{
  guint i;
  guint rv;
  if (n_strs == 0)
    return 5003;

  /* don't bother rehashing the first string */
  rv = XML_STRING_GET_PREAMBLE (strs[0])->hash;

  for (i = 1; i < n_strs; i++)
    {
      const char *str = GSK_XML_STR (strs[i]);
      while (str[i])
        {
          rv += (rv << 5);
          rv += (guint8) *str++;
        }
    }
  return rv;
}

/* possibly double the hash-table size,
   returning TRUE if a resize occurs. */
static gboolean
maybe_resize_for_addition ()
{
  if (max_strs == n_strs)
    {
      guint old_n_bins = 1 << n_bins_log2;
      Preamble **new_bins = g_new (Preamble *, old_n_bins * 2);
      guint hilo_bit = 1 << n_bins_log2;
      guint i;
      n_bins_log2++;
      for (i = 0; i < old_n_bins; i++)
        {
          Preamble *hi_bin = NULL;
          Preamble *lo_bin = NULL;
          Preamble *at = bins[i];
          while (at)
            {
              Preamble *next = at->next_in_bin;
              if (at->hash & hilo_bit)
                {
                  at->next_in_bin = hi_bin;
                  hi_bin = at;
                }
              else
                {
                  at->next_in_bin = lo_bin;
                  lo_bin = at;
                }
              at = next;
            }
          new_bins[i] = lo_bin;
          new_bins[i + old_n_bins] = hi_bin;
        }
      return TRUE;
    }
  else
    return FALSE;
}

GskXmlString *gsk_xml_string_new        (const char         *str)
{
  guint hash;
  guint bin;
  Preamble *pre;
  guint len;

  hash = my_hash (str);
  bin = hash & ((1 << n_bins_log2) - 1);
  for (pre = bins[bin]; pre != NULL; pre = pre->next_in_bin)
    if (strcmp (PREAMBLE_TO_STR (pre), str) == 0)
      return gsk_xml_string_ref (PREAMBLE_TO_STRING (pre));

  if (maybe_resize_for_addition ())
    bin = hash & ((1 << n_bins_log2) - 1);

  len = strlen (str);
  pre = g_malloc (sizeof (Preamble) + len + 1);
  pre->ref_count = 1;
  pre->hash = hash;
  strcpy ((char*)(pre + 1), str);
  pre->next_in_bin = bins[bin];
  bins[bin] = pre;
  n_strs++;
  return (GskXmlString *) (pre + 1);
}

GskXmlString *gsk_xml_string_new_len    (const char         *str,
                                         guint               len)
{
  guint hash;
  guint bin;
  Preamble *pre;

  hash = my_hash_len (str, len);
  bin = hash & ((1 << n_bins_log2) - 1);
  for (pre = bins[bin]; pre != NULL; pre = pre->next_in_bin)
    if (memcmp (PREAMBLE_TO_STR (pre), str, len) == 0
      && PREAMBLE_TO_STR(pre)[len] == '\0')
      return gsk_xml_string_ref (PREAMBLE_TO_STRING (pre));

  if (maybe_resize_for_addition ())
    bin = hash & ((1 << n_bins_log2) - 1);

  pre = g_malloc (sizeof (Preamble) + len + 1);
  pre->ref_count = 1;
  pre->hash = hash;
  strcpy ((char*)(pre + 1), str);
  pre->next_in_bin = bins[bin];
  bins[bin] = pre;
  return (GskXmlString *) (pre + 1);
}

GskXmlString *gsk_xml_strings_concat    (guint               n_strs,
                                         GskXmlString      **strs)
{
  guint hash = my_hash_strarray (n_strs, strs);
  guint bin = hash & ((1 << n_bins_log2) - 1);
  Preamble *pre;
  guint i;
  guint len;
  char *at;
  for (pre = bins[bin]; pre != NULL; pre = pre->next_in_bin)
    if (hash == pre->hash)
      {
        const char *orig_at = PREAMBLE_TO_STR (pre);
        for (i = 0; i < n_strs; i++)
          {
            const char *at = GSK_XML_STR (strs[i]);
            while (*at && *orig_at == *at)
              {
                at++;
                orig_at++;
              }
            if (*at == 0)
              break;
          }
        if (i == n_strs && *orig_at == 0)
          return gsk_xml_string_ref (PREAMBLE_TO_STRING (pre));
      }

  if (maybe_resize_for_addition ())
    bin = hash & ((1 << n_bins_log2) - 1);

  len = 0;
  for (i = 0; i < n_strs; i++)
    len += strlen (GSK_XML_STR (strs[i]));

  pre = g_malloc (sizeof (Preamble) + len + 1);
  pre->ref_count = 1;
  pre->hash = hash;
  at = (char*)(pre + 1);
  for (i = 0; i < n_strs; i++)
    at = g_stpcpy (at, GSK_XML_STR (strs[i]));
  pre->next_in_bin = bins[bin];
  bins[bin] = pre;
  return (GskXmlString *) (pre + 1);
}




GskXmlString *gsk_xml_string_ref        (const GskXmlString *str)
{
  GskXmlString *rv = (GskXmlString *) str;
  Preamble *preamble = ((Preamble *) rv) - 1;
  g_assert (preamble->ref_count > 0);
  ++preamble->ref_count;
  return rv;
}

void          gsk_xml_string_unref      (GskXmlString       *str)
{
  Preamble *preamble = ((Preamble *) str) - 1;
  g_assert (preamble->ref_count > 0);
  if (--preamble->ref_count == 0)
    {
      guint hash = preamble->hash;
      Preamble **pat;
      guint bin = hash & ((1 << n_bins_log2) - 1);
      pat = bins + bin;
      while (*pat != preamble)
        pat = &((*pat)->next_in_bin);
      *pat = preamble->next_in_bin;
      g_free (preamble);
      n_strs--;
    }
}


syntax highlighted by Code2HTML, v. 0.9.1