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

/* is a t_hash_code_proc.
   This algorithm is taken from dragon book, p.436.
   Returns a hash code */
t_hash_code		dict_hash_code_dragon(buf,len)
char			*buf;
int			len;
{
  t_hash_code		hash_code;
  t_hash_code		carry;

  hash_code = 0;
  for ( ; len > 0; buf++, len--) 
    {
      hash_code = (hash_code << 4) + (*buf);
      if (carry = (hash_code & 0xf0000000)) 
	{
	  hash_code ^= (carry >> 24);
	  hash_code ^= carry;
	}
    }
  return (hash_code);
}

/* is a t_hash_code_proc.
   This algorithm is taken from the public domain.
   Returns a hash code */
t_hash_code		dict_hash_code_public(buf,len)
char			*buf;
int			len;
{
   t_hash_code		hash_code;
   t_hash_code		value;
   
   hash_code = 0;
   for (;len > 0;len--)
   {
      value = *buf++ & 0xFF;
      hash_code += value * value;
   }
   return (hash_code);
}

/* is a t_hash_code_proc.
   This algorithm is taken from the GNU smalltalk.
   Returns a hash code */
t_hash_code		dict_hash_code_gst(buf,len)
char			*buf;
int			len;
{
  t_hash_code		result;

  result = 0L;
  for (; len > 0; buf++, len--)
    {
      result = (result << 1);
      result ^= *buf;
    }
  return (result);
}

/* instantiates a new dictionary.
   A dictionary (t_dict) is a container,
   it stores references of objects according to a key (a string).
   A dictionary is a hash table combined with an hash_code_proc.
   Returns a new dict structure, if NULL status is filled */
t_dict			*dict_new(base,
				  list_base,
				  hash_code_proc,
				  alloc_algorithm_proc,
				  alloc_proc,
				  realloc_proc,
				  free_proc,
				  comment,
				  status)
int			base;			/* Hash size used	*/
int			list_base;		/* Hash list size used	*/
t_dict_hash_code_proc	hash_code_proc;		/* Hash_code_proc used	*/
t_alloc_algorithm_proc	alloc_algorithm_proc;	/* Alloc. strategy	*/
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		*/
{
  t_dict		*dict;
  
  if ((dict = alloc_proc(sizeof (t_dict),
			 comment,
			 "dict_new:dict",
			 status)) == NULL)
    return (NULL);
  if ((dict->ht = hash_new(base,
			   list_base,
			   alloc_algorithm_proc,
			   alloc_proc,
			   realloc_proc,
			   free_proc,
			   comment,
			   status)) == NULL)
    {
      free_proc(dict,
		comment,
		"dict_new:dict");
      return (NULL);
    } 
  dict->hash_code_proc = hash_code_proc;
  return (dict);
}

/* is a t_hash_destroy_proc.
   Frees the key of the hash_elt. */
VOID_FUNC		dict_destroy_elt(he,dict)
t_hash_elt		*he;
t_dict			*dict;
{
  dict->ht->free_proc(he->key,
		      dict->ht->comment,
		      "*:key");
}

/* frees all keys but not the hash_table nor the structure itself.
   It doesn't touch references. 
   After this action, dict is "empty" and reusable. */
VOID_FUNC		dict_destroy(dict)
t_dict			*dict;
{
  hash_destroy(dict->ht,
	       (t_hash_destroy_proc)dict_destroy_elt,
	       dict);
}

/* frees all keys and the hash table and the structure itself.
   It doesn't touch references. */
VOID_FUNC		dict_delete(dict)
t_dict			*dict;
{
  t_free_proc		free_proc;
  char			*comment;

  free_proc = dict->ht->free_proc;
  comment = dict->ht->comment;
  hash_delete(dict->ht,
	      (t_hash_destroy_proc)dict_destroy_elt,
	      dict);
  free_proc(dict,
	    comment,
	    "*:dict");
}

/* returns the hash_elt according to key.
   Returns NULL if not found. */
t_hash_elt		*dict_get(dict,key)
t_dict			*dict;
char			*key;
{
  return (hash_get(dict->ht,
		   dict->hash_code_proc(key,strlen(key)),
		   (t_hash_cmp_proc)strcmp,
		   key));
}

/* adds a new record.
   Returns 0 if OK, ERR_MG_EXIST if the key is already registered and might
   returns various errors from subroutines. */
t_status		dict_add(dict,key,value)
t_dict			*dict;
char			*key;
VOID_PTR		value;	/* Reference to an object	*/
{
  t_hash_elt		*he;

  if (he = dict_get(dict,key))
    return (ERR_MG_EXIST);
  else
    {
      char		*nkey;
      t_status		status;
      
      if ((nkey = strdup_alloc(key,
			       dict->ht->alloc_proc,
			       dict->ht->comment,
			       "dict_add:key",
			       &status)) == NULL)
	return (status);
      if ((status = hash_add(dict->ht,
			     dict->hash_code_proc(nkey,strlen(nkey)),
			     nkey,
			     value)) != 0)
	return (status);
    }
  return (0);
}

/* adds-or-overrides a record.
   Returns 0 if OK, otherwise might returns various errors. */
t_status		dict_override(dict,key,value)
t_dict			*dict;
char			*key;
VOID_PTR		value;	/* Reference to an object */
{
  t_hash_elt		*he;

  if (he = dict_get(dict,key))
    {
      he->value = value;
      return (0);
    }
  else
    return (dict_add(dict,key,value));
}
  
/* removes a record.
   Note that it doesn't touch the value.
   Returns 0 if OK, otherwise various errors. */
t_status		dict_rm(dict,key)
t_dict			*dict;
char			*key;
{
  return (hash_rm(dict->ht,
		  dict->hash_code_proc(key,strlen(key)),
		  (t_hash_cmp_proc)strcmp,
		  key,
		  (t_hash_destroy_proc)dict_destroy_elt,
		  dict));
}

/* tells if the dictionary is empty or not.
   Returns TRUE or FALSE. */
t_boolean		dict_is_empty(dict)
t_dict			*dict;
{
  VEC_FOR(dict->ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  if (TRUE) /* avoid warning */
	    return (FALSE);
	}
      VEC_ENDFOR;
    }
  VEC_ENDFOR;
  return (TRUE);
}

/* counts the number of objects referenced in the dictionary.
   Returns the count */
int			dict_count(dict)
t_dict			*dict;
{
  int			count;

  count = 0;
  VEC_FOR(dict->ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  count++;
	}
      VEC_ENDFOR;
    }
  VEC_ENDFOR;
  return (count);
}

/* walks a dictionary without any order.
   Returns 0 if OK. If proc returns a negative status, it breaks and returns
   the proc status. */
t_status		dict_walk(dict,proc,data)
t_dict			*dict;
t_dict_walk_proc	proc;
VOID_PTR		data;	/* Data passed to proc	*/
{
  VEC_FOR(dict->ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  t_status	status;

	  if ((status = proc(he,data)) != 0)
	    return (status);
	}
      VEC_ENDFOR;
    }
  VEC_ENDFOR;
  return (0);
}	

/* converts a dictionary to a vector of hash_elt.
   It is not intended for direct usage.
   Returns 0 if OK, otherwise might returns various errors */
t_status		dict_to_vec_hash_elt(dict,vec)
t_dict			*dict;
t_vec			*vec;	/* Simple vector */
{
  VEC_FOR(dict->ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  t_status	status;

	  if ((status = vec_add(vec,he)) != 0)
	    return (status);
	}
      VEC_ENDFOR;
    }
  VEC_ENDFOR;
  return (0);
}

/* walks a dictionary according to a user-defined order.
   It is the meta version of dict_walk_sorted(3).
   Returns 0 if OK, might returns various errors. */
t_status		dict_walk_sorted_cmp(dict,walk_proc,cmp_proc,data)
t_dict			*dict;
t_dict_walk_proc	walk_proc;
t_vec_cmp_proc		cmp_proc;
VOID_PTR		data;
{
  t_status		status;
  t_vec			*vec;

  status = 0;
  if ((vec = vec_new(VEC_BASE,
		     FALSE,
		     dict->ht->alloc_algorithm_proc,
		     dict->ht->alloc_proc,
		     dict->ht->realloc_proc,
		     dict->ht->free_proc,
		     dict->ht->comment,
		     &status)) == NULL)
    return (status);
  if ((status = dict_to_vec_hash_elt(dict,vec)) != 0)
    goto bad;
  vec_sort(vec,cmp_proc);
  VEC_FOR(vec,t_hash_elt *sorted_he)
    {
      t_status		status;
      
      if ((status = walk_proc(sorted_he,data)) != 0)
	goto bad;
    }
  VEC_ENDFOR;
bad:
  vec_delete(vec);
  return (status);
}	

/* is a t_vec_cmp_proc.
   It causes the dictionary to be ordered according ASCII table.
   Returns negative, zero or positive value */
int			dict_walk_sorted_cmpproc(p1,p2)
VOID_PTR		*p1;	/* Pointer to a hash_elt pointer */
VOID_PTR		*p2;	/* Pointer to a hash_elt pointer */
{
  t_hash_elt		*he1;
  t_hash_elt		*he2;

  he1 = (t_hash_elt *)(*p1);
  he2 = (t_hash_elt *)(*p2);
  return (strcmp(he1->key,he2->key));
}

/* walk the dictionary according to the ASCII order of the keys.
   Returns 0 if OK. If proc returns a negative status, it breaks and returns
   the proc status. */   
t_status		dict_walk_sorted(dict,walk_proc,data)
t_dict			*dict;
t_dict_walk_proc	walk_proc;
VOID_PTR		data;
{
  return (dict_walk_sorted_cmp(dict,
			       walk_proc,
			       (t_vec_cmp_proc)dict_walk_sorted_cmpproc,
			       data));
}

/* is a t_dict_walk_proc.
   Used internally for dict_max_len(3). */
t_status		dict_max_len_walk(he,max_len)
t_hash_elt		*he;
int			*max_len;
{
  int			len;

  len = strlen(he->key);
  if (len > (*max_len))
    *max_len = len;
  return (0);
}

/* computes the max_len of all the keys.
   Returns 0. *Should not* returns anything else */
int			dict_max_len(dict,status)
t_dict			*dict;
t_status		*status;
{
  int			max_len;
  
  max_len = 0;
  if (((*status) = dict_walk(dict,
			     (t_dict_walk_proc)dict_max_len_walk,
			     &max_len)) != 0)
    return (-1);
  return (max_len);
}

/* is a t_dict_walk_proc.
   It is used internally by dict_get_from_value(3) */
t_status			dict_get_from_value_walk(he,dgfvd)
t_hash_elt			*he;
t_dict_get_from_value_data	*dgfvd;
{
  if (dgfvd->value == he->value)
    {
      dgfvd->he = he;
      return (ERR_MG_BREAK);
    }
  return (0);
}

/* gets the *first* hash_elt whith the matching value.
   Returns an hash_elt or NULL with a filled status */
t_hash_elt			*dict_get_from_value(dict,value,status)
t_dict				*dict;
VOID_PTR			value;
t_status			*status;
{
  t_dict_get_from_value_data	dgfvd;
  
  dgfvd.value = value;
  if (((*status) = dict_walk(dict,
			     (t_dict_walk_proc)dict_get_from_value_walk,
			     &dgfvd)) != 0)
    {
      if (*status == ERR_MG_BREAK)
	return (dgfvd.he);
    }
  else
    (*status) = ERR_MG_NOT_FOUND;
  return (NULL);
}

/* changes the key of a record.
   Returns 0 if OK, might returns ERR_MG_IDENTICAL if keys are the same, 
   ERR_MG_NO_SUCH_KEY if the key doesn't exist. Note that nothing is done in
   such cases. It can also returns various other errors from subroutines */
t_status		dict_change_key(dict,old_key,new_key)
t_dict			*dict;
char			*old_key;
char			*new_key;
{
  t_hash_elt		*he;
  t_status		status;

  if (!strcmp(old_key,new_key))
    return (ERR_MG_IDENTICAL);
  if ((he = dict_get(dict,old_key)) == NULL)
    return (ERR_MG_NO_SUCH_KEY);
  if ((status = dict_add(dict,new_key,he->value)) != 0)
    return (status);
  if ((status = dict_rm(dict,old_key)) != 0)
    return (status);
  return (0);  
}

#ifdef DEBUG
/* is a t_dict_walk_proc.
   It is used internally by dict_show(3) and dict_show_sorted(3). */
t_status		dict_show_walk(he,unused)
t_hash_elt		*he;
VOID_PTR		unused;
{
  fprintf(stderr,"key=`%s' value=0x%x\n",he->key,he->value);
  return (0);
}

/* shows a dictionary.
   This is a debug function. */
VOID_FUNC		dict_show(dict)
t_dict			*dict;
{
  dict_walk(dict,(t_dict_walk_proc)dict_show_walk,NULL);
}

/* shows a dictionary (sorted).
   This is a debug function */
VOID_FUNC		dict_show_sorted(dict)
t_dict			*dict;
{
  dict_walk_sorted(dict,(t_dict_walk_proc)dict_show_walk,NULL);
}
#endif
