/* Nessuslib -- the Nessus Library
 * Copyright (C) 1998 Renaud Deraison
 *
 * $Id: harglists.c,v 1.12 1999/11/27 10:43:54 renaud Exp $
 *
 * Author: Jordan Hrycaj <jordan@mjh.teddy-net.com>
 *
 * 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.
 *
 * Jordan re-wrote Renauds idea of an arglists management on top
 * of the hash list manager
 */

#include "hlst.h"

#define __HARG_INTERNAL__
#include "harglists.h"

#define EXPORTING
#include <includes.h>

/* ------------------------------------------------------------------------- *
 *                      private definitions                                  *
 * ------------------------------------------------------------------------- */

/* XMALLOC returns memory initialized to zero */
#define XMALLOC(x) emalloc(x)
#define XFREE(x)   efree(&(x))


typedef /* general data type record/slot */
struct _harg {
  hargtype_t type;
  unsigned     id;
  unsigned   size;

/* type ARG_STRING, ARG_STUCT data are stored as a data block
   of given size - all other data types are stored as (void*) */
  struct { char data [1];} d ;
  /* varable length, struct aligned  */
} harg;


/* easy access to the data field */
#define HARG_DATA(type, h)  (*(type*)((h)->d.data))


typedef /* call back function's state buffer */
struct _do_for_all_cb_state {
  void *state ;
  int (*cb_fn)(void*,void*,hargtype_t,unsigned,int,hargkey_t*) ;
} do_for_all_cb_state ;


/* ------------------------------------------------------------------------- *
 *                   private functions: call back                            *
 * ------------------------------------------------------------------------- */

static void
clean_up
  (harglst     *a,
   harg     *data,
   hargkey_t *key,
   unsigned   len)
{
  /* last step, delete descriptor */
  if (data == 0) {
    if (a == 0) return ;
    a->x = 0 ; /* add some robustness */
    XFREE (a) ;
    return ;
  }
  if (data->type == HARG_HARGLST) {
    if (!a->destroy_sublist) return ;
    /* recursively delete sublist */
    harg_close_all (HARG_DATA (harglst*, data));
  }
#ifdef ARG_ARGLIST
  else if (data->type == HARG_ARGLIST) {
    if (!a->destroy_sublist) return ;
    /* recursively delete sublist */
    arg_free_all (HARG_DATA (struct arglist*, data));
  }
#endif
  if (data != 0)
    XFREE (data);
}

static harg*
a_copy
  (harglst     *a,
   harg     *data,
   hargkey_t *key,
   unsigned   len)
{
  unsigned size = sizeof (harg) + data->size - 1;
  return
    memcpy (XMALLOC (size), data, size) ;
}

static int 
do_for_all_cb
  (do_for_all_cb_state *s, 
   harg                *h,
   hargkey_t         *key, 
   unsigned           len)
{
  void *value ;

  switch (h->type) {
  case HARG_STRING :
  case HARG_BLOB :
    value = h->d.data ;
    break ;
  default:
    value = HARG_DATA (void*,h) ;
  }
  return (s->cb_fn) (s->state, value, h->type, h->size, h->id, key);
}

/* ------------------------------------------------------------------------- *
 *               private functions: data tree dumper                         *
 * ------------------------------------------------------------------------- */

static void
do_newlevel
  (void)
{
  fputs ("\n", stderr);
}

static void
do_indent
  (int level)
{
  while (level -- > 0)
    fputs ("   ", stderr);
  fputs (" ", stderr);
}

static void
do_printf
  (const char *f,
   harg      **R,
   void       *a)
{
  char *s = (R != 0) ? query_key_hlst ((void**)R) : "*NULL*" ;
  fprintf (stderr, "<%s> = ", s);
  fprintf (stderr, f, a);
  fputs ("\n", stderr);
}


static void
do_harg_dump
  (harglst *a,
   int  level)
{
  hsrch *w ;
  harg **R, *r ;

  if(a == 0 || (w = open_hlst_search (a->x)) == 0) {
    do_printf ("Error; no such list!\n",0,0);
    return;
  }
 
  while ((R = (harg**)next_hlst_search (w)) != 0) {
    do_indent (level);
    if ((r = *R) == 0) {
      do_printf ("Warning: NULL entry in list\n",0,0);
      continue ;
    }
    switch(r->type) {
    case HARG_STRING : 
      do_printf ("%s", R, (void*)r->d.data);
      continue ;
    case HARG_BLOB : 
      do_printf ("%#x", R, (void*)r->d.data);
      continue ;
#ifdef ARG_ARGLIST
    case ARG_ARGLIST :
      do_newlevel ();
      do_printf ("(old mode>) sublist ...", R, 0);
      arg_dump (HARG_DATA (struct arglist*, r), level+1) ;
      continue ;
#endif
    case HARG_HARGLST :
      /* do_newlevel (); */
      do_printf ("sublist ...", R, 0);
      do_harg_dump (HARG_DATA (harglst*, r), level+1) ;
      continue ;
    case HARG_INT :
      do_printf ("%d", R, (void*)HARG_DATA (int, r));
      continue ;
    default:
      do_printf ("%#x", R, (void*)HARG_DATA (int, r));
    }
  }
  
  close_hlst_search (w);
}

/* ------------------------------------------------------------------------- *
 *                      private functions                                    *
 * ------------------------------------------------------------------------- */

static harg*
create_harg
  (hargtype_t type,
   void      *data,
   unsigned   size)
{
  harg *h = XMALLOC (sizeof (harg) + size - 1);
  h->type = type ;
  h->size = size ;
  
  switch (type) {
  case HARG_STRING :
    if (size != 0) /* last character is '\0' */
      h->d.data [ -- size] = '\0' ;
    /* continue below */
  case HARG_BLOB :
      if (size != 0 && data != 0)
	memcpy (h->d.data, data, size);
      break ;
  default:
    *(void**)h->d.data = data ;
  }
  
  return h;
}


static harg*
get_harg_entry
  (harglst      *a,
   const char *key)
{
  harg **R ;

  return a == 0 || (R = (harg**)find_hlst (a->x, key, 0)) == 0
    ? 0
    : *R 
    ;
}

static hargkey_t*
harg_add_entry
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type,
   unsigned   size,
   void     *value,
   int   overwrite)
{
  harg **R, *r ;

  /* sanity check */
  if (a == 0 || type == HARG_NONE) 
    return 0;

  if (value == 0)
    size = 0 ;

  switch (type) {
  case HARG_STRING :
    if (size == 0) /* need a terminating '\0' */
      size = (value == 0) ? 0 : (strlen (value) + 1) ;
    else
      size ++ ;
    /* continue below */
  case HARG_BLOB :
    break ;
  default:
    size = sizeof (void*) ;
  }
  
  if ((R = (harg**)find_hlst (a->x, key, 0)) != 0) {
    r = *R ;

    /* record exists, do we need to overwrite ? */
    if (!overwrite && type == r->type)
      return query_key_hlst ((void**)R);

    if (r->size == size) {
      /* the sizes did not change, reuse data block */
      r->type = type ;
      if((type == HARG_STRING)||
         (type == HARG_BLOB))
	 {
          if (size)
	   memcpy (r->d.data, value, size);
	 }
      else 
         {
	  *(void**)r->d.data = value;
	 }
      return 0 ;
    }
    /* sizes have changed - reallocate */
    *R = create_harg (type, value, size);
    (*R)->id = r->id ;
    XFREE (r);
    return 0 ;
  }

  /* no such value - create anew */
  R = (harg**)make_hlst (a->x, key, 0) ;
  *R = create_harg (type, value, size);
  if (((*R)->id = ++ a->autoid) == 0)
    (*R)->id = ++ a->autoid ;
     
  return query_key_hlst ((void**)R);
}

/* ------------------------------------------------------------------------- *
 *                 public functions: open/close management                   *
 * ------------------------------------------------------------------------- */

harglst*
harg_create 
  (unsigned size)
{
  harglst* h = XMALLOC (sizeof (harglst)) ;
  if ((h->x = create_hlst   /* FIXME: set some default value for size ? */
       (size, (void(*)(void*,void*,char*,unsigned))clean_up, h)) == 0) {
    XFREE (h);
    return 0;
  }
  return h ;
}

void
harg_close
  (harglst*a)
{
  if (a == 0) return ;
  a->destroy_sublist = 0 ;
  destroy_hlst (a->x);
}


void
harg_close_all
  (harglst*a)
{
  if (a == 0) return ;
  a->destroy_sublist = 1 ;
  destroy_hlst (a->x);
}

harglst*
harg_dup
  (harglst*    a,
   unsigned size)
{
  harglst* h ;
  /* sanity check */
  if (a == 0) return 0 ;
  h = XMALLOC (sizeof (harglst)) ;
  if ((h->x = copy_hlst 
       (a->x, size, 
	(void*(*)(void*,void*,char*,unsigned))a_copy, a,
	(void (*)(void*,void*,char*,unsigned))clean_up, 0)) == 0) {
    XFREE (h);
    return 0;
  }
  return h ;
}


/* ------------------------------------------------------------------------- *
 *               public functions: varable access - modify                   *
 * ------------------------------------------------------------------------- */

hargkey_t*
harg_add
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type,
   unsigned   size,
   void     *value)
{
  return harg_add_entry
    (a, key, type, size, value, 1 /* do overwrite */);
}

hargkey_t*
harg_add_default
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type,
   unsigned   size,
   void     *value)
{
  return harg_add_entry
    (a, key, type, size, value, 0 /* don't overwrite */);
}

int
harg_set_tvalue
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type,
   unsigned   size,
   void     *value)
{
  harg **R, *r ;

  /* sanity check */
  if (a == 0) 
    return -1;

  if ((R = (harg**)find_hlst (a->x, key, 0)) == 0)
    return -1 ;

  r = *R ;

  /* check whether we want strict type checking */
  if (type != HARG_NONE && r->type != type)
    return -1 ;
  
  if (value == 0)
    size = 0 ;
  
  switch (r->type) {
  case HARG_STRING :
    if (size == 0) /* need a terminating '\0' */
      size = (value == 0) ? 0 : (strlen (value) + 1) ;
    else
      size ++ ;
    /* continue below */
  case HARG_BLOB :
    break ;
  default:
    *(void**)r->d.data = value ;
    return 0;
  }

  if (r->size != size) {
    /* reallocate that entry */
    *R = create_harg (r->type, value, size);
    XFREE (r);
    return 0 ;
  } 

  if (value != 0)
    memcpy (r->d.data, value, size);
  return 0;
}


int
harg_set_type
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type)
{
  harg **R, *r ;

  /* sanity check */
  if (a == 0 || type == HARG_NONE) 
    return -1;

  if ((R = (harg**)find_hlst (a->x, key, 0)) == 0)
    return -1 ;

  r = *R ;
  
  if (r->type == type)
    return 0 ; /* nothing to do */

  switch (r->type) {
  case HARG_STRING :
  case HARG_BLOB :
    switch (type) {
    case HARG_STRING :
    case HARG_BLOB :
      r->type = type ;
      return 0;
    }
    return -1 ; /* cannot change to ptr/int value */
  }

  switch (type) {
  case HARG_STRING :
  case HARG_BLOB :
    return -1 ; /* cannot change to struct/string value */
  }
  r->type = type ;
  return 0;
}


int
harg_set_id
  (harglst     *a,
   hargkey_t *key,
   int         id)
{
  harg *r ;
  if ((r = get_harg_entry (a, key)) == 0) return 0;
  return r->id = id ;
}

int
harg_remove
  (harglst     *a,
   hargkey_t *key)
{
  return delete_hlst (a->x, key, 0);
}



/* ------------------------------------------------------------------------- *
 *               public functions: the key is a pointer                      *
 * ------------------------------------------------------------------------- */
hargkey_t*
 harg_ptr_add_ptr(a, ptr)
 harglst * a;
 void * ptr;
{
 unsigned size;
 harg ** R, *r;
 if(!a)
  return 0;
 
 size = sizeof(void*);
 if((R = (harg**)find_hlst(a->x, (char*)(&ptr), sizeof(ptr))))
 {
   r = *R;
   r->type = HARG_PTR;
   *(void**)r->d.data = ptr ;
   return 0;
 }
 
 /*
  * Create a new entry
  */
 R = (harg**)make_hlst(a->x, (char*)&ptr, sizeof(ptr));
 *R = create_harg(HARG_PTR, ptr, size);
 if (((*R)->id = ++ a->autoid) == 0)
    (*R)->id = ++ a->autoid ;
 return query_key_hlst ((void**)R);
}

void * harg_ptr_get_ptr(a, ptr)
 harglst * a;
 void * ptr;
{
 harg **R, *r;
 if(a==0)return 0;
 R = (harg**)find_hlst (a->x, (char*)&ptr, sizeof(ptr));
 if(!R)return 0;
 r = *R;
 return HARG_DATA (void*, r);
}

int harg_ptr_remove_ptr(a, ptr)
 harglst * a;
 void * ptr;
{
 return delete_hlst (a->x, (char*)&ptr, sizeof(ptr));
}

/* ------------------------------------------------------------------------- *
 *               public functions: varable access - retrieve                 *
 * ------------------------------------------------------------------------- */

void * /* same as above, but with type check */
harg_get_tvalue
  (harglst      *a,
   hargkey_t  *key,
   hargtype_t type)
{
  harg *r ;   /* FIXME, check Renauds source */
  
  if ((r = get_harg_entry (a, key)) == 0 || 
      /* check for strict type checking */
      type != HARG_NONE && type != r->type) 
    return 0;
  
  switch (r->type) {
  case HARG_STRING :
  case HARG_BLOB :
    return r->d.data ;
  }
  return HARG_DATA (void*, r) ;
}


hargkey_t *
harg_inx_key
  (harglst   *a,
   unsigned inx)
{
  void **R ;
  if (a == 0) 
    return 0;
  sort_hlst (a->x);
  if ((R = inx_hlst (a->x, inx)) == 0)
    return 0 ;
  return query_key_hlst (R);
}

hargtype_t
harg_get_type
  (harglst     *a,
   hargkey_t *key)
{
  harg *r ;
  if ((r = get_harg_entry (a, key)) == 0) return HARG_NONE ;
  return r->type ; 
}

int
harg_get_id
  (harglst     *a,
   hargkey_t *key)
{
  harg *r ;
  if ((r = get_harg_entry (a, key)) == 0) return -1;
  return r->id ; 
}


unsigned
harg_get_size
   (harglst * a,
    hargkey_t *key)
{
  harg *r;
  if((r = get_harg_entry(a, key)) == 0) return -1;
  return r->size;
}


hargwalk*
harg_walk_init
  (harglst *a)
{
  if (a == 0) return 0;
  return (hargwalk*)open_hlst_search (a->x);
}

hargkey_t*
harg_walk_next
  (hargwalk *w)
{
  harg **P ;
  if ((P = (harg**)next_hlst_search ((hsrch*)w)) != 0)
    return query_key_hlst ((void**)P) ;
  close_hlst_search ((hsrch*)w);
  return 0;
}

void
harg_walk_stop
  (hargwalk *w)
{
  close_hlst_search ((hsrch*)w);
}

int
harg_do
  (harglst    *a,
   int (*fn) (void *,void*,hargtype_t,unsigned,int,hargkey_t*),
   void *state)
{
  do_for_all_cb_state s ;
  if (a == 0) return -1 ;
  s.state = state ;
  if ((s.cb_fn = fn) == 0) return -1 ;
  return for_hlst_do 
    (a->x, (int(*)(void*,void*,char*,unsigned))do_for_all_cb, &s);
}

void
harg_dump
  (harglst *a)
{
  if (a == 0) return ;
  do_harg_dump (a, 0);
  /* hlst_statistics (a->x, 0, 0); not tested, yet */
}
