/* 
 Copyright (C) 2006-2013  Ladislav Vaiz <ok1zia@nagano.cz>
    and authors of glib www.gtk.org
 * */

/* GLIB - Library of useful routines for C programming
 * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * 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.
 */

/*
 * Modified by the GLib Team and others 1997-1999.  See the AUTHORS
 * file for a list of people on the GLib Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GLib at ftp://ftp.gtk.org/pub/gtk/. 
 */

/* 
 * MT safe
 */


#include <zhash.h>
#include <zthread.h>

#include <glib.h>

#define HASH_TABLE_MIN_SIZE 11
#define HASH_TABLE_MAX_SIZE 13845163



static void		z_hash_table_resize	 (ZHashTable	*hash_table);
static ZHashNode**	z_hash_table_lookup_node (ZHashTable	*hash_table, gconstpointer key);
static ZHashNode*	z_hash_node_new		 (gpointer	 key, gpointer value);
static void		z_hash_node_destroy	 (ZHashNode	*hash_node);
static void		z_hash_nodes_destroy	 (ZHashNode	*hash_node);

//G_LOCK_DEFINE_STATIC (z_hash_global);
//static GMutex *z_hash_global_mutex;
#ifdef Z_LEAK_DEBUG_LIST
char *z_hash_global_file;
int z_hash_global_line;
#endif


ZHashTable*
z_hash_table_new (GHashFunc    hash_func,
		  GCompareFunc key_compare_func)
{
  ZHashTable *hash_table;
  int i;
  
  hash_table = g_new (ZHashTable, 1);
  hash_table->size = HASH_TABLE_MIN_SIZE;
  hash_table->nnodes = 0;
  hash_table->frozen = FALSE;
  hash_table->hash_func = hash_func ? hash_func : g_direct_hash;
  hash_table->key_compare_func = key_compare_func;
  hash_table->nodes = g_new (ZHashNode*, hash_table->size);
  
  for (i = 0; i < hash_table->size; i++)
    hash_table->nodes[i] = NULL;
  
  return hash_table;
}

void
z_hash_table_destroy (ZHashTable *hash_table)
{
  gint i;
  
  g_return_if_fail (hash_table != NULL);
  
  for (i = 0; i < hash_table->size; i++)
    z_hash_nodes_destroy (hash_table->nodes[i]);
  
  g_free (hash_table->nodes);
  g_free (hash_table);
}

static inline ZHashNode**
z_hash_table_lookup_node (ZHashTable	*hash_table,
			  gconstpointer	 key)
{
  ZHashNode **node;
  
  node = &hash_table->nodes
    [(* hash_table->hash_func) (key) % hash_table->size];
  
  /* Hash table lookup needs to be fast.
   *  We therefore remove the extra conditional of testing
   *  whether to call the key_compare_func or not from
   *  the inner loop.
   */
  if (hash_table->key_compare_func)
    while (*node && !(*hash_table->key_compare_func) ((*node)->key, key))
      node = &(*node)->next;
  else
    while (*node && (*node)->key != key)
      node = &(*node)->next;
  
  return node;
}

gpointer
z_hash_table_lookup (ZHashTable	  *hash_table,
		     gconstpointer key)
{
  ZHashNode *node;
  
  g_return_val_if_fail (hash_table != NULL, NULL);
  
  node = *z_hash_table_lookup_node (hash_table, key);
  
  return node ? node->value : NULL;
}

void
z_hash_table_insert (ZHashTable *hash_table,
		     gpointer	 key,
		     gpointer	 value)
{
  ZHashNode **node;
  
  g_return_if_fail (hash_table != NULL);
  
  node = z_hash_table_lookup_node (hash_table, key);
  
  if (*node)
    {
      /* do not reset node->key in this place, keeping
       * the old key might be intended.
       * a z_hash_table_remove/z_hash_table_insert pair
       * can be used otherwise.
       *
       * node->key = key; */
      (*node)->value = value;
    }
  else
    {
      *node = z_hash_node_new (key, value);
      hash_table->nnodes++;
      if (!hash_table->frozen)
	z_hash_table_resize (hash_table);
    }
}

void
z_hash_table_remove (ZHashTable	     *hash_table,
		     gconstpointer    key)
{
  ZHashNode **node, *dest;
  
  g_return_if_fail (hash_table != NULL);
  
  node = z_hash_table_lookup_node (hash_table, key);

  if (*node)
    {
      dest = *node;
      (*node) = dest->next;
      z_hash_node_destroy (dest);
      hash_table->nnodes--;
  
      if (!hash_table->frozen)
        z_hash_table_resize (hash_table);
    }
}

gboolean
z_hash_table_lookup_extended (ZHashTable	*hash_table,
			      gconstpointer	 lookup_key,
			      gpointer		*orig_key,
			      gpointer		*value)
{
  ZHashNode *node;
  
  g_return_val_if_fail (hash_table != NULL, FALSE);
  
  node = *z_hash_table_lookup_node (hash_table, lookup_key);
  
  if (node)
    {
      if (orig_key)
	*orig_key = node->key;
      if (value)
	*value = node->value;
      return TRUE;
    }
  else
    return FALSE;
}

/*static void z_hash_table_freeze (ZHashTable *hash_table)
{
  g_return_if_fail (hash_table != NULL);
  
  hash_table->frozen++;
}

static void z_hash_table_thaw (ZHashTable *hash_table)
{
  g_return_if_fail (hash_table != NULL);
  
  if (hash_table->frozen)
    if (!(--hash_table->frozen))
      z_hash_table_resize (hash_table);
} */

guint
z_hash_table_foreach_remove (ZHashTable	*hash_table,
			     GHRFunc	 func,
			     gpointer	 user_data)
{
  ZHashNode *node, *prev;
  int i;
  guint deleted = 0;
  
  g_return_val_if_fail (hash_table != NULL, 0);
  g_return_val_if_fail (func != NULL, 0);
  
  for (i = 0; i < hash_table->size; i++)
    {
    restart:
      
      prev = NULL;
      
      for (node = hash_table->nodes[i]; node; prev = node, node = node->next)
	{
	  if ((* func) (node->key, node->value, user_data))
	    {
	      deleted += 1;
	      
	      hash_table->nnodes -= 1;
	      
	      if (prev)
		{
		  prev->next = node->next;
		  z_hash_node_destroy (node);
		  node = prev;
		}
	      else
		{
		  hash_table->nodes[i] = node->next;
		  z_hash_node_destroy (node);
		  goto restart;
		}
	    }
	}
    }
  
  if (!hash_table->frozen)
    z_hash_table_resize (hash_table);
  
  return deleted;
}

void
z_hash_table_foreach (ZHashTable *hash_table,
		      GHFunc	  func,
		      gpointer	  user_data)
{
  ZHashNode *node;
  gint i;
  
  g_return_if_fail (hash_table != NULL);
  g_return_if_fail (func != NULL);
  
  for (i = 0; i < hash_table->size; i++)
    for (node = hash_table->nodes[i]; node; node = node->next)
      (* func) (node->key, node->value, user_data);
}

/* Returns the number of elements contained in the hash table. */
guint
z_hash_table_size (ZHashTable *hash_table)
{
  g_return_val_if_fail (hash_table != NULL, 0);
  
  return hash_table->nnodes;
}

static void
z_hash_table_resize (ZHashTable *hash_table)
{
  ZHashNode **new_nodes;
  ZHashNode *node;
  ZHashNode *next;
  gfloat nodes_per_list;
  guint hash_val;
  gint new_size;
  gint i;
  
  nodes_per_list = (gfloat) hash_table->nnodes / (gfloat) hash_table->size;
  
  if ((nodes_per_list > 0.3 || hash_table->size <= HASH_TABLE_MIN_SIZE) &&
      (nodes_per_list < 3.0 || hash_table->size >= HASH_TABLE_MAX_SIZE))
    return;
  
  new_size = CLAMP(g_spaced_primes_closest (hash_table->nnodes),
		   HASH_TABLE_MIN_SIZE,
		   HASH_TABLE_MAX_SIZE);
  new_nodes = g_new0 (ZHashNode*, new_size);
  
  for (i = 0; i < hash_table->size; i++)
    for (node = hash_table->nodes[i]; node; node = next)
      {
	next = node->next;

	hash_val = (* hash_table->hash_func) (node->key) % new_size;

	node->next = new_nodes[hash_val];
	new_nodes[hash_val] = node;
      }
  
  g_free (hash_table->nodes);
  hash_table->nodes = new_nodes;
  hash_table->size = new_size;
}

static ZHashNode*
z_hash_node_new (gpointer key,
		 gpointer value)
{
	ZHashNode *hash_node;
  
	hash_node = g_new(ZHashNode, 1);
	hash_node->key = key;
	hash_node->value = value;
	hash_node->next = NULL;
  
	return hash_node;
}

static void z_hash_node_destroy (ZHashNode *hash_node)
{
    g_free(hash_node);
}

static void z_hash_nodes_destroy (ZHashNode *hash_node)
{
	if (hash_node){
		ZHashNode *node = hash_node;
  
		while (node->next){
			ZHashNode *n = node->next;
			g_free(node);
			node = n;
		}
	}
}




gboolean free_gpointer_item(gpointer key, gpointer value, gpointer user_data){
    if (key) g_free(key);
    if (value) g_free(value);
    return TRUE;
}

#ifndef Z_HAVE_G_HASH_TABLE_REMOVE_ALL
void g_hash_table_remove_all(GHashTable *table){
    // FIXME does not work if g_hash_table_new_full is created by other free handlers than g_free
    g_hash_table_foreach_remove(table, free_gpointer_item, NULL); 
}
#endif
