/* Nessuslib -- the Nessus Library
 * Copyright (C) 1998 Renaud Deraison
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Arglists management
 */

#define EXPORTING
#include <includes.h>

#include <glib.h>

#define HASH_MAX 2713

/**
 * @brief Make a hash value from string.
 * 
 * Hash vlaues of argument names are used to speed up the lookups when calling
 * arg_get_value().
 */
static int
mkhash_arglists (const char * name)
{
 unsigned long h = 0;
 const unsigned char *p = (const unsigned char*) name;

 if (name == NULL) return 0;

 while (*p != '\0')
   h = (h * 129) + *p++;
 return h % HASH_MAX;
}

/**
 * @brief Struct to cache names (keys) of arglist entries.
 * 
 * A lot of entries in our arglists have the same name.
 * We use a caching system to avoid to allocate twice the same name
 * 
 * This saves about 300Kb of memory, with minimal performance impact
 */
struct name_cache {
	char * name;
	int occurences;
	struct name_cache * next;
	struct name_cache * prev;
	};


static int cache_inited = 0;
static struct name_cache cache[HASH_MAX+1];


static void cache_init()
{
 int i;
 for(i=0;i<HASH_MAX+1;i++)
 	{
	bzero(&(cache[i]), sizeof(cache[i]));
	}
 cache_inited = 1;
}

static struct name_cache * 
cache_get_name(const char* name, int h)
{
 struct name_cache * nc;
 
 if(cache_inited == 0)
 	cache_init();
	
 if(!name)
  return NULL;
  
 nc = cache[h].next;
  
 while(nc != NULL)
 {
  if(nc->name != NULL && 
    !strcmp(nc->name, name))
    	return nc;
  else 
  	nc = nc->next;
 }
 return NULL;
}

static struct name_cache *
cache_add_name(const char* name, int h)
{
 struct name_cache * nc;

 if(name == NULL)
  return NULL;
 
 
 nc = emalloc(sizeof(struct name_cache));
 nc->next = cache[h].next;
 nc->prev = NULL;
 nc->name = estrdup(name);
 nc->occurences = 1;
 if ( cache[h].next != NULL )
  cache[h].next->prev = nc;
 
 cache[h].next = nc;
 
 return nc;
}

char *
cache_inc(const char * name)
{
  struct name_cache * nc;
  int h = mkhash_arglists(name);
  nc = cache_get_name(name, h);
 if(nc != NULL)
  nc->occurences ++;
 else
   nc = cache_add_name(name, h);
 return nc->name;
}


void 
cache_dec(const char * name)
{
 struct name_cache* nc;
 int h;

 if(!name)
  return;

 h = mkhash_arglists(name);
 nc  = cache_get_name(name, h);
 if( nc == NULL)
 {
  /*
  fprintf(stderr, "libnessus: cache_dec(): non-existant name\n");
  */
  return;
 }
 
 nc->occurences --;
 if( nc->occurences == 0 ){
 	 efree(&nc->name);
	 if(nc->next != NULL)
	  nc->next->prev = nc->prev;
	  
	 if(nc->prev != NULL)
	  nc->prev->next = nc->next;
	 else
	  cache[h].next = nc->next;
	 
	 efree(&nc);
	}
}




ExtFunc void 
arg_free_name (char * name)
{
  cache_dec(name);
}


ExtFunc void 
arg_add_value(arglst, name, type, length, value)
  struct arglist * arglst;
  const char * name;
  int type;
  long length;
  void * value;
{
  int	h;

	if(!arglst)return;
	while(arglst->next)arglst = arglst->next;
	
	if (type == ARG_STRUCT) {
    	 void* new_val = emalloc(length);
     	 memcpy(new_val, value, length);
    	 value = new_val;
   	}

	h = mkhash_arglists(name);
	arglst->name = cache_inc(name);
	arglst->value = value;
	arglst->length = length;
	arglst->type = type;
	arglst->next = emalloc(sizeof(struct arglist));
	arglst->hash = h;
}


static void
init_element(struct arglist * arglst, const char * name, int type,
    long length, void * value)
{
  int	h;
  if (type == ARG_STRUCT) {
    void* new_val = emalloc(length);
    memcpy(new_val, value, length);
    value = new_val;
  }

  h = mkhash_arglists(name);
  arglst->name = cache_inc(name);
  arglst->value = value;
  arglst->length = length;
  arglst->type = type;
  arglst->hash = h;
}


/**
 * Like arg_add_value but inserts the new element near the beginning
 * instead of the end.  This is much faster for long lists but leads to
 * a different order of the elements.
 * @see arg_add_value
 */
ExtFunc void 
arg_add_value_at_head(struct arglist * arglst, const char * name, int type,
    long length, void * value)
{
  if(!arglst)
    return;

  if (arglst->next)
  {
    struct arglist * element = emalloc(sizeof(struct arglist));
    init_element(element, name, type, length, value);
    element->next = arglst->next;
    arglst->next = element;
  }
  else
  {
    arglst->next = emalloc(sizeof(struct arglist));
    init_element(arglst, name, type, length, value);
  }
}


static struct arglist * arg_get(struct arglist * arg, const char * name)
{
  int h = mkhash_arglists(name);
 if(arg == NULL)
  return NULL;
 
 while(arg->next != NULL)
 {
   if(arg->hash == h && strcmp(arg->name, name) == 0)
    return arg;
  else
   arg = arg->next;
 }
 return NULL;
}




ExtFunc int 
arg_set_value(arglst, name, length, value)
 struct arglist * arglst;
 const char * name;
 long length;
 void *value;
{
 
 if(name == NULL)
  return -1;
  
 arglst = arg_get(arglst, name);
  
  if(arglst != NULL)
    {
      if (arglst->type == ARG_STRUCT) {
	void* new_val = emalloc(length);
	if (arglst->value) efree(&arglst->value);
	memcpy(new_val, value, length);
	value = new_val;
      }
      arglst->value = value;
      arglst->length = length;
      return 0;
    }
  else return -1; 
}

ExtFunc int 
arg_set_type(arglst, name, type)
 struct arglist * arglst;
 const char * name;
 int type;
{
  arglst = arg_get(arglst, name);
  if(arglst == NULL)
   return -1;
   
  if (arglst->type == ARG_STRUCT  &&  type != ARG_STRUCT) {
    efree(&arglst->value);
  }
  arglst->type = type;
  return 0;
}


ExtFunc void * 
arg_get_value(args, name)
 struct arglist * args;
 const char * name;
{

  if(args == NULL)
   return NULL;
  
  args = arg_get(args, name);
  if(args == NULL)
   return NULL;
  else  
  return(args->value);
}


ExtFunc int 
arg_get_length(args,name)
 struct arglist * args;
 const char * name;
{
  args = arg_get(args, name);
  if(args != NULL)
    return(args->length);
  else 
    return 0;
}


ExtFunc int 
arg_get_type(args,name)
 struct arglist * args;
 const char * name;
{
 args = arg_get(args, name);
 if( args != NULL )
    return(args->type);
  else 
    return -1;
}


ExtFunc void arg_dup(dst, src)
 struct arglist * dst;
 struct arglist * src;
{
 if(!src)
  return;
  
 while(src->next)
 {
   dst->name = cache_inc(src->name);
  dst->type = src->type;
  dst->length = src->length;
  dst->hash = src->hash;
  switch(src->type)
  {
   case ARG_INT :
   case ARG_PTR : 
    dst->value = src->value;
    break;
    
   case ARG_STRING :
    if(src->value){
     dst->value = estrdup((char*)src->value);
    }
    break;
    
   case ARG_STRUCT :
     if (src->value) {
       dst->value = emalloc(src->length);
       memcpy(dst->value, src->value, src->length);
       dst->length = src->length;
     }
     break;

 
  case ARG_ARGLIST :
    dst->value = emalloc(sizeof(struct arglist));
    arg_dup((struct arglist *)dst->value, (struct arglist *)src->value);
    break;
  }
  dst->next = emalloc(sizeof(struct arglist));
  dst = dst->next;
  src = src->next;
 }
}


ExtFunc void 
arg_dump(args, level)
 struct arglist * args;
 int level;
{
	const char * spaces = "--------------------";
	if(!args)
	{
		printf("Error ! args == NULL\n");
		return;
	}
	
	if(args)
	 while(args->next)
	 {
		switch(args->type)
		{
			case ARG_STRING :
			
			fprintf(stderr, "%sargs->%s : %s\n",spaces+(20-level),
			  args->name,
			  (char *)args->value);
			break;
			case ARG_ARGLIST :
			
			fprintf(stderr, "%sargs->%s :\n", spaces+(20-level),
			  args->name);
			  arg_dump(args->value, level+1);
			break;
			case ARG_INT :
			fprintf(stderr, "%sargs->%s : %d\n",spaces+(20-level),
			  args->name,
			  (int)GPOINTER_TO_SIZE(args->value));
			break;
			default :
			fprintf(stderr, "%sargs->%s : %d\n",spaces+(20-level),
			  args->name,
			  (int)GPOINTER_TO_SIZE(args->value));
			break;
		}
		args = args->next;
	}
}


ExtFunc void
arg_free(arg)
 struct arglist* arg;
{
 while(arg)
 {
  struct arglist * next = arg->next;
  cache_dec(arg->name);
  efree(&arg);
  arg = next;
 }
}


ExtFunc void arg_free_all(arg)
 struct arglist* arg;
{
 while(arg)
 {
  struct arglist * next = arg->next;
  switch(arg->type)
  {
   case ARG_ARGLIST :
    arg_free_all(arg->value);
    break;
   case ARG_STRING :
    efree(&arg->value);
    break;
   case ARG_STRUCT :
    efree(&arg->value);
    break;
  }
  cache_dec(arg->name);
  efree(&arg);
  arg = next;
 }
}

void 
arg_del_value(args, name)
 struct arglist * args;
 const char * name;
{
  int h = mkhash_arglists(name);
  struct arglist * pivot;
  struct arglist * element = NULL;
  struct arglist store;

  if (args == NULL)
    return;
 
  pivot = args;

  while (pivot->next != NULL) {
    if (pivot->hash == h && strcmp(pivot->name, name) == 0) {
      element = pivot;
      break;
    }
    pivot = pivot->next;
 }

  if (!element || element->hash != h || strcmp(element->name, name))
    return;

  if (args == element) {
    element = args->next;
    memcpy(&store, element, sizeof(struct arglist));
    memcpy(element, args, sizeof(struct arglist));
    memcpy(args, &store, sizeof(struct arglist));
  } else {
    pivot = args;
    while (pivot->next != NULL && pivot->next != element)
      pivot = pivot->next;
    pivot->next = element->next;
  }
  element->next = NULL;

  arg_free(element);
}
