/*
** mg_hash.c for  in 
** 
** Made by vianney rancurel
** Login   <vianney@epita.fr>
** 
** Started on  Wed Aug 25 12:15:30 1999 vianney rancurel
** Last update Thu Oct 28 20:16:07 1999 
*/
#include "mg.h"

/* instantiates a new hash table.
   A hash table is a vector of vectors,
   it stores references of objects according to a hash code (t_hash_code).
   The hash list is determined by hash_code modulus hash_size. The lists
   (as vectors) can grow infinitely. 
   Returns a new vec_vec structure, if NULL status is filled */
t_vec			*hash_new(base,
				  list_base,
				  alloc_algorithm_proc,
				  alloc_proc,
				  realloc_proc,
				  free_proc,
				  comment,
				  status)
int			base;			/* Number of slots */
int			list_base;	/* Base number of elts in each slot */
t_alloc_algorithm_proc	alloc_algorithm_proc;	/* Alloc strategy for slots */
t_alloc_proc		alloc_proc;		/* Alloc. method */
t_realloc_proc		realloc_proc;		/* Realloc method */
t_free_proc		free_proc;		/* Free method */
char			*comment;		/* Major comment */
t_status		*status;		/* Various errors */
{
  t_vec			*ht;
  int			i;
  
  if ((ht = vec_new(base,
		    TRUE,
		    alloc_algorithm_proc,
		    alloc_proc,
		    realloc_proc,
		    free_proc,
		    comment,
		    status)) == NULL)
    return (NULL);
  i = 0;
  while (i < base)
    {
      t_vec		*hl;

      if (((hl = vec_new(list_base,
			 FALSE,
			 alloc_algorithm_proc,
			 alloc_proc,
			 realloc_proc,
			 free_proc,
			 comment,
			 status)) == NULL) ||
	  (((*status) = vec_add(ht,hl)) != 0))
	{
	  int		j;

	  j = 0;
	  while (j < i)
	    {
	      vec_delete(VEC_AT(ht,j));
	      j++;
	    }
	  vec_delete(ht);
	  return (NULL);
	}
      i++;
    }
  assert(VEC_COUNT(ht) == base);
  return (ht);
}

/* destroys all the hash_lists and the hash_elts but not the hash_table itself.
   Note that he->key and he->value are opaque for the hash table,
   E.g.: Dictionaries manages string keys,
   Dictionaries of strings (dict_str) manages
   string keys and string values,
   Ids manages pointer keys. There are also many other containers managing
   keys and values...
   It calls destroy_proc before freeing the hash_elt. */
VOID_FUNC		hash_destroy(ht,destroy_proc,data)
t_vec			*ht;
t_hash_destroy_proc	destroy_proc;
VOID_PTR		data;		/* Data passed to destroy_proc */
{
  VEC_FOR(ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  destroy_proc(he,data);
	  ht->free_proc(he,
			ht->comment,
			"*:he");
	}
      VEC_ENDFOR;
      vec_destroy(hl);
    }
  VEC_ENDFOR;
}

/* destroys the hash_lists, the hash_elts and the hash_table itself.
   It acts nearly the same as hash_destroy(3). */
VOID_FUNC		hash_delete(ht,destroy_proc,data)
t_vec			*ht;
t_hash_destroy_proc	destroy_proc;
VOID_PTR		data;
{
  VEC_FOR(ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  destroy_proc(he,data);
	  ht->free_proc(he,
			ht->comment,
			"*:he");
	}
      VEC_ENDFOR;
      vec_delete(hl);
    }
  VEC_ENDFOR;
  vec_delete(ht);
}

/* adds a new key/value pair.
   Note that it isn't hash_table which manages associations between
   hash codes and keys. See dictionaries and ids.
   Returns 0 if OK, might return various errors */
t_status		hash_add(ht,hcode,key,value)
t_vec			*ht;
t_hash_code		hcode;		/* The hash code */
VOID_PTR		key;		/* The opaque key */
VOID_PTR		value;		/* The opaque value */
{
  t_vec			*hl;
  t_hash_elt		*he;
  t_status		status;

  hl = VEC_AT_MODULO(ht,hcode);
  if ((he = ht->alloc_proc(sizeof (t_hash_elt),
			   ht->comment,
			   "hash_add:he",
			   &status)) == NULL)
    return (status);
  he->key = key;
  he->value = value;
  if ((status = vec_add(hl,he)) != 0)
    {
      ht->free_proc(he,
		    ht->comment,
		    "hash_add:he");
      return (status);
    }
  return (0);
}

/* gets the hash_elt associated with hash_code.
   Returns the hash_elt or NULL if not found */
t_hash_elt		*hash_get(ht,hcode,cmp_proc,key)
t_vec			*ht;
t_hash_code		hcode;
t_hash_cmp_proc		cmp_proc; /* E.g. strcmp(3) for dictionaries */
VOID_PTR		key;
{
  t_vec			*hl;

  hl = VEC_AT_MODULO(ht,hcode);
  VEC_FOR(hl,t_hash_elt *he)
    {
      if (!cmp_proc(he->key,key))
	return (he);
    }
  VEC_ENDFOR;
  return (NULL);
}

/* removes the key/value pair associated with hash code.
   Returns 0 if OK, ERR_MG_NOENT if not found. 
   Might not return anything else */
t_status		hash_rm(ht,hcode,cmp_proc,key,destroy_proc,data)
t_vec			*ht;
t_hash_code		hcode;
t_hash_cmp_proc		cmp_proc;
VOID_PTR		key;
t_hash_destroy_proc	destroy_proc;
VOID_PTR		data;		/* Data passed to destroy_proc */
{
  t_vec			*hl;

  hl = VEC_AT_MODULO(ht,hcode);
  VEC_FOR(hl,t_hash_elt *he)
    {
      if (!cmp_proc(he->key,key))
	{
	  destroy_proc(he,data);
	  VEC_RM_ELT(hl,he);
	  ht->free_proc(he,
			ht->comment,
			"*:he");
	  return (0);
	}
    }
  VEC_ENDFOR;
  return (ERR_MG_NOENT);
}

#ifdef DEBUG
t_boolean		hash_more_verbose = FALSE;

/* shows hash table repartitions.
   It shows the average count of hash lists, and the diff average between
   all differences to the average count. If the global variable
   hash_more_verbose if TRUE, it also shows details for each slot.
   It is used to check the efficiency of a t_hash_code_proc.
   This is a debug function.
   Note: We could used a derivated version of this function to re-arrange
   dynamically the hash size to its best value. */
VOID_FUNC		hash_show_repartition(ht)
t_vec			*ht;
{
  int			total;
  int			average;
  int			diff_total;
  int			diff_average;

  total = 0;
  VEC_FOR(ht,t_vec *hl)
    {
      total += VEC_COUNT(hl);
    }
  VEC_ENDFOR;
  average = total / VEC_COUNT(ht);
  fprintf(stderr,"average: %d\n",average);
  diff_total = 0;
  VEC_FOR(ht,t_vec *hl)
    {
      int		diff;
      
      diff = VEC_COUNT(hl) - average;
      if (hash_more_verbose)
	fprintf(stderr,"slot %d: %d (%s%d)\n",
		VEC_IDX,
		VEC_COUNT(hl),
		(diff < 0)?"-":"+",
		MY_ABS(diff));
      diff_total += MY_ABS(diff);
    }
  VEC_ENDFOR;
  diff_average = diff_total / VEC_COUNT(ht);
  fprintf(stderr,"diff average: %d\n",diff_average);
}
#endif
