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

/* instantiates a new "id" object.
   Ids are containers, they are hash tables of pointers. They are very useful
   to do an indirection between 2 references. Note that an id is a special
   case of a hash table: it is a hash table where the hash codes ARE the keys. 
   Returns a new id structure or NULL if something has failed. */
t_id			*id_new(base,
				list_base,
				alloc_algorithm_proc,
				alloc_proc,
				realloc_proc,
				free_proc,
				comment,
				status)
int			base;			/* Hash size	*/
int			list_base;		/* Hash lists base size */
t_alloc_algorithm_proc	alloc_algorithm_proc;	/* Alloc. strategy for lists*/
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;		/* Filled if NULL */
{
  t_id			*id;
  
  if ((id = alloc_proc(sizeof (t_id),
		       comment,
		       "id_new:id",
		       status)) == NULL)
    return (NULL);
  if ((id->ht = hash_new(base,
			 list_base,
			 alloc_algorithm_proc,
			 alloc_proc,
			 realloc_proc,
			 free_proc,
			 comment,
			 status)) == NULL)
    {
      free_proc(id,
		comment,
		"id_new:id");
      return (NULL);
    } 
  return (id);
}

/* is a t_hash_destroy_proc.
   It is used internally by id_destroy(3) and id_delete(3). 
   Note that this function does nothing because key is opaque 
   (it's a pointer value). */
VOID_FUNC		id_destroy_elt(he,data)
t_hash_elt		*he;
VOID_PTR		data;
{
  /* NOP */
}

/* "destroys" an id's hash table without destroying the structure itself.
   See hash_destroy(3). After this action, id is "empty" and reusable. */
VOID_FUNC		id_destroy(id)
t_id			*id;
{
  hash_destroy(id->ht,
	       id_destroy_elt,
	       NULL);
}

/* "destroys" an ids hash table and the structure itself */
VOID_FUNC		id_delete(id)
t_id			*id;
{
  t_free_proc		free_proc;
  char			*comment;

  free_proc = id->ht->free_proc;
  comment = id->ht->comment;
  hash_delete(id->ht,
	      id_destroy_elt,
	      NULL);
  free_proc(id,
	    comment,
	    "*:id");
}

/* is a t_hash_cmp_proc.
   It is used internally, as the "strcmp" for pointers: it
   performs pointer difference (as ptrdiff_t in Linux). */
int			id_cmp(key1,key2)
VOID_PTR		key1;
VOID_PTR		key2;
{
  return ((char *)key2 - (char *)key1);
}

/* gets the hash_elt associated with key.
   Returns the hash_elt or NULL */
t_hash_elt		*id_get(id,key)
t_id			*id;
VOID_PTR		key;
{
  return (hash_get(id->ht,
		   (t_hash_code)key,
		   (t_hash_cmp_proc)id_cmp,
		   key));
}

/* adds a new key/value pair.
   Returns 0 if OK, ERR_MG_EXIST if keys exists, might also return various
   errors */
t_status		id_add(id,key,value)
t_id			*id;
VOID_PTR		key;
VOID_PTR		value;
{
  t_status		status;

  if (id_get(id,key))
    return (ERR_MG_EXIST);
  else
    {
      if ((status = hash_add(id->ht,
			     (t_hash_code)key,
			     key,
			     value)) != 0)
	return (status);
    }
  return (0);
}

/* adds a new unique key/value pair.
   First, id_add_unique(3) tries to create a key/value pair with a key
   equals to base_key. If it fails, it increments base_key and so on.
   The unique key found is returned in id_ret. Note it's nearly
   improbable that all the keys could be used!
   Returns 0 if OK, might return various errors */
t_status		id_add_unique(id,base_key,value,id_ret)
t_id			*id;
VOID_PTR		base_key;
VOID_PTR		value;
VOID_PTR		*id_ret;	/* The unique key found	*/
{
  t_status		status;

  while (id_get(id,base_key))
    base_key = (char *)base_key + 1;
  if ((status = hash_add(id->ht,
			 (t_hash_code)base_key,
			 base_key,
			 value)) != 0)
    return (status);
  (*id_ret) = base_key;
  return (0);
}

/* destructive-adding of a key/value pair.
   If key is not found, a new pair is created.
   Returns 0 if OK, might return various errors */
t_status		id_override(id,key,value)
t_id			*id;
VOID_PTR		key;
VOID_PTR		value;
{
  t_hash_elt		*he;
  
  if (he = id_get(id,key))
    {
      he->value = value;
      return (0);
    }
  else
    return (id_add(id,key,value));
}

/* removes a key/value pair.
   Returns 0 if OK, might returns various errors (see hash_rm(3)) */
t_status		id_rm(id,key)
t_id			*id;
VOID_PTR		key;
{
  return (hash_rm(id->ht,
		  (t_hash_code)key,
		  (t_hash_cmp_proc)id_cmp,
		  key,
		  (t_hash_destroy_proc)id_destroy_elt,
		  id));
}

/* tells if the container is empty.
   Returns TRUE or FALSE */
t_boolean		id_is_empty(id)
t_id			*id;
{
  VEC_FOR(id->ht,t_vec *hl)
    {
      VEC_FOR(hl,t_hash_elt *he)
	{
	  if (TRUE) /* AVOID WARNING */
	    return (FALSE);
	}
      VEC_ENDFOR;
    }
  VEC_ENDFOR;
  return (TRUE);
}

/* Returns the count of key/value associations. */
int			id_count(id)
t_id			*id;
{
  int			count;

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

/* walks an id unordered.
   As in dict_walk(3), breaks if proc returns a negative status.
   Returns 0 if OK, the status of proc if not. */
t_status		id_walk(id,proc,data)
t_id			*id;
t_id_walk_proc		proc;
VOID_PTR		data;		/* data passed to proc */
{
  VEC_FOR(id->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 an id to a vector of hash elts.
   It is used internally by id_walk_sorted_cmp(3). */
t_status		id_to_vec_hash_elt(id,vec)
t_id			*id;
t_vec			*vec;
{
  VEC_FOR(id->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 an id in a user-defined sorted way.
   This is the "meta" version of id_walk_sorted.
   Returns 0 if OK, breaks if walk_proc fails (as in id_walk(3)) then
   returns the status of walk_proc */
t_status		id_walk_sorted_cmp(id,walk_proc,cmp_proc,data)
t_id			*id;
t_id_walk_proc		walk_proc;
t_vec_cmp_proc		cmp_proc;
VOID_PTR		data;		/* Data passed to walk_proc */
{
  t_status		status;
  t_vec			*vec;

  status = 0;
  if ((vec = vec_new(VEC_BASE,
		     FALSE,
		     id->ht->alloc_algorithm_proc,
		     id->ht->alloc_proc,
		     id->ht->realloc_proc,
		     id->ht->free_proc,
		     id->ht->comment,
		     &status)) == NULL)
    return (status);
  if ((status = id_to_vec_hash_elt(id,vec)) != 0)
    {
      goto bad;
    }
  vec_sort(vec,(t_vec_cmp_proc)cmp_proc);
  VEC_FOR(vec,t_hash_elt *sorted_he)
    {
      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 is used internally by id_walk_sorted(3). */
int			id_walk_sorted_cmpproc(p1,p2)
VOID_PTR		*p1;
VOID_PTR		*p2;
{
  t_hash_elt		*he1;
  t_hash_elt		*he2;

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

/* walks an id following the ptr diff of keys.
   It uses id_cmp(3) to compare keys.
   Returns 0 if OK, breaks if walk_proc fails (as in id_walk(3)) then
   returns the status of walk_proc */
t_status		id_walk_sorted(id,proc,data)
t_id			*id;
t_id_walk_proc		proc;
VOID_PTR		data;
{
  return (id_walk_sorted_cmp(id,
			     proc,
			     (t_vec_cmp_proc)id_walk_sorted_cmpproc,
			     data));
}	

#ifdef DEBUG
/* is a t_id_walk_proc.
   It is used internally by id_show(3). */
t_status		id_show_walk(he)
t_hash_elt		*he;
{
  fprintf(stderr,"0x%x=0x%x",he->key,he->value);
  return (0);
}

/* Shows a id.
   It is a debug function. */
VOID_FUNC		id_show(id)
t_id			*id;
{
  t_status		status;

  status = id_walk(id,
		   (t_id_walk_proc)id_show_walk,
		   NULL);
  assert(status == 0);
}
#endif

/* adds a value with a unique positive integer key to an id.
   As ids manage void pointer keys (wide opaque integers), it there manages
   integer keys in the range of INT_MIN to INT_MAX (restricted to 32 bits
   in a 64 bit machine).
   This function operates from 0 to INT_MAX, this
   allows the user to consider positive references as valid and negative
   references as errors.
   Id_ret is the new unique key.
   Returns 0 if OK, it might also return various errors */
t_status	id_add_with_unique_positive_int_key(id,base_key,value,id_ret)
t_id			*id;
int			base_key;	/* The integer key */
VOID_PTR		value;		/* The opaque value */
int			*id_ret;	/* The unique positive key */
{
  t_status		status;

  if (base_key < 0)
    base_key = 0;
  while (id_get(id,(VOID_PTR)base_key))
    {
      base_key = base_key + 1;
      if (base_key < 0)
	base_key = 0;
    }
  if ((status = hash_add(id->ht,
			 (t_hash_code)base_key,
			 (VOID_PTR)base_key,
			 value)) != 0)
    return (status);
  (*id_ret) = base_key;
  return (0);
}

#ifdef DEBUG
/* is a t_id_walk_proc.
   It is used internally by id_int_show(3). */
t_status		id_show_keys_as_ints_walk(he)
t_hash_elt		*he;
{
  fprintf(stderr,"%d=%x\n",he->key,he->value);
  return (0);
}

/* shows an id_int.
   It operates like id_show(3) with the difference that key is printed
   as a decimal number. */
VOID_FUNC		id_show_keys_as_ints(id)
t_id			*id;
{
  t_status		status;

  status = id_walk(id,
		   (t_id_walk_proc)id_show_keys_as_ints_walk,
		   NULL);
  assert(status == 0);
}
#endif
