/*
 * SCHASH.C - routines to manipulate hash tables
 *          - intended to be a complete module that
 *          - can be used with any application by defining
 *          - the struct, hashel, in SCORE.H suitably
 *
 * Source Version: 2.0
 * Software Release #92-0043
 *
 */

#include "cpyright.h"

#include "score.h"

SC_THREAD_LOCK(SC_hash_lock);

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _SC_HASH - compute hash value for string s in a table of size */

static long _SC_hash(s, size)
   char *s;
   int size;
   {long v, r, m;

    m = (long) size;

    for (v = 0L; *s != '\0'; )
        v = (v << 1L) ^ (*s++);

    r = ((long) abs(v)) % m;

    return(r);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_LOOKUP - lookup s in hash table, table */

hashel *SC_lookup(s, tab)
   char *s;
   HASHTAB *tab;
   {hashel *np, **tb;
    int sz;
    long hv;

    if (tab == NULL)
       return(NULL);

    sz = tab->size;
    tb = tab->table;
    hv = _SC_hash(s, sz);
    for (np = tb[hv]; np != NULL; np = np->next)
        if (strcmp(s, np->name) == 0)
           return(np);                                          /* found it */

    return(NULL);}                                             /* not found */

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_DEF_LOOKUP - return a pointer to the object version of LOOKUP */

byte *SC_def_lookup(s, tab)
   char *s;
   HASHTAB *tab;
   {hashel *np;
   
    if (tab == NULL)
       return(NULL);

    np = SC_lookup(s, tab);
    if (np != NULL)
       return(np->def);
    else
       return(NULL);}
    
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_INSTALL - install an object in the hash table and mark it
 *            - the object type is defined in hash.h and is generic
 *            - to enhance the portability of this code
 *            - WARNING: do NOT use literals or volatiles for the type;
 *            -          for efficiency they are not strsavef'd !!!
 */

hashel *SC_install(name, obj, type, tab)
   char *name;
   byte *obj;
   char *type;
   HASHTAB *tab;
   {return (_SC_install(name, obj, type, tab, TRUE, TRUE));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _SC_INSTALL - install an object in the hash table
 *             - the object type is defined in hash.h and is generic
 *             - to enhance the portability of this code
 *             - WARNING: do NOT use literals or volatiles for the type;
 *             -          for efficiency they are not strsave'd !!!
 */

hashel *_SC_install(name, obj, type, tab, mark, lookup)
   char *name;
   byte *obj;
   char *type;
   HASHTAB *tab;
   int mark, lookup;
   {hashel *np, **tb;
    int sz;
    long hv;

    SC_LOCKON(SC_hash_lock);

    sz = tab->size;
    tb = tab->table;
 
    if (lookup)
       np = SC_lookup(name, tab);
    else
       np = NULL;

/* if not found install it */
    if (np == NULL)
       {np = FMAKE(hashel, "_SC_INSTALL:np");
        if (np == NULL)
           {SC_LOCKOFF(SC_hash_lock);
            return(NULL);}

        np->name = SC_strsavef(name, "char*:_SC_INSTALL:name");
        if (np->name == NULL)
           {SC_LOCKOFF(SC_hash_lock);
            return(NULL);}

        hv       = _SC_hash(np->name, sz);
        np->next = tb[hv];
        tb[hv]   = np;
        (tab->nelements)++;}

    np->type = type;
    np->def  = obj;

    if (mark)
       {np->free = TRUE;
        SC_mark(obj, 1);}
    else
       np->free = FALSE;

    SC_LOCKOFF(SC_hash_lock);

    return(np);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_HASH_REM - remove the specified entry from the given hash table
 *             - return TRUE if successfully removed and
 *             - otherwise return FALSE
 */

int SC_hash_rem(name, tab)
   char *name;
   HASHTAB *tab;
   {hashel *np, *nxt, **tb;
    int sz;
    long i;

    sz = tab->size;
    tb = tab->table;
    i  = _SC_hash(name, sz);
    np = tb[i];

/* if not found nothing else to do */
    if (np == NULL)
       return(FALSE);
    else
       {SC_LOCKON(SC_hash_lock);
        if (strcmp(name, np->name) == 0)
           {tb[i] = np->next;

/* undo the MARK in SC_install */
            if (np->free == TRUE)
	       SFREE(np->def);
            SFREE(np->name);
            SFREE(np);
            (tab->nelements)--;
            SC_LOCKOFF(SC_hash_lock);
            return(TRUE);}

/* otherwise search for it */
        else
           for (; np->next != NULL; np = np->next)
               {nxt = np->next;
                if (strcmp(name, nxt->name) == 0)
                   {np->next = nxt->next;
		    if (np->free == TRUE)
		      SFREE(nxt->def);
                    SFREE(nxt->name);
                    SFREE(nxt);
                    (tab->nelements)--;
                    SC_LOCKOFF(SC_hash_lock);
                    return(TRUE);};};};
   
    SC_LOCKOFF(SC_hash_lock);
    return(FALSE);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_HASH_CLR - clear the specified hash table */

void SC_hash_clr(tab)
   HASHTAB *tab;
   {int i, sz;
    hashel **tb, *np, *nxt;

    sz = tab->size;
    tb = tab->table;
    for (i = 0; i < sz; i++)
        {for (np = tb[i]; np != NULL; np = nxt)
             {nxt = np->next;

/* undo the MARK in SC_install */
	      if (np->free == TRUE)
		 SFREE(np->def);
              SFREE(np->name);
              SFREE(np);};
         tb[i] = NULL;};

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_MAKE_HASH_TABLE - allocate and initialize a hash table of size, sz
 *                    - returns a HASHTAB pointer
 */

HASHTAB *SC_make_hash_table(sz, docflag)
   int sz, docflag;
   {HASHTAB *tab;
    hashel **tb;
    int i;

/* allocate a new hash table */
    tab = FMAKE(HASHTAB, "SC_MAKE_HASH_TABLE:tab");

    if (tab == NULL)
       {printf("\nCannot allocate a new hash table of size %d\n", sz);
        return(NULL);};

    tb = FMAKE_N(hashel *, sz, "SC_MAKE_HASH_TABLE:tb");
    if (tb == NULL)
       return(NULL);

    tab->size      = sz;
    tab->docp      = docflag;
    tab->nelements = 0;
    tab->table     = tb;

/* explicitly NULL the pointers */
    for (i = 0; i < sz; i++)
        tb[i] = NULL;

    return(tab);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_RL_HASH_TABLE - release a hash table
 *                  - call SC_HASH_CLR first to release the contents
 *                  - of the table
 */

void SC_rl_hash_table(tab)
   HASHTAB *tab;
   {SC_hash_clr(tab);

    SFREE(tab->table);
    SFREE(tab);

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _SC_DUMP_HASH - return an array of pointers whose entries point to the
 *               - installed names in the given hash table
 */

char **_SC_dump_hash(tab, patt, type, sort)
   HASHTAB *tab;
   char *patt, *type;
   int sort;
   {hashel *np, **tb;
    char **lineptr, *name;
    int i, sz, nlines;

    if (tab == NULL)
       return(NULL);

/* allocate a list of pointers to the names in the hash table */
    lineptr = FMAKE_N(char *, tab->nelements, "SC_HASH_DUMP:lineptr");
    if (lineptr == NULL)
       return(NULL);

/* fill in the list of pointers to names in the hash table */
    sz = tab->size;
    tb = tab->table;

    nlines = 0;
    for (i = 0; i < sz; i++)
        for (np = tb[i]; np != NULL; np = np->next)
            {if ((type != NULL) && (strcmp(type, np->type) != 0))
	        continue;
	        
	     name = np->name;
             if (patt == NULL)
                lineptr[nlines++] = name;

             else if (SC_regx_match(name, patt))
                lineptr[nlines++] = name;};

/* check that the number of names found is what is expected */
    if (nlines > tab->nelements)
       return(NULL);

    REMAKE_N(lineptr, char *, nlines + 1);
    lineptr[nlines] = NULL;

/* sort the names */
    if (sort)
       SC_string_sort(lineptr, nlines);

/* return the list of names */
    return(lineptr);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_DUMP_HASH - return an array of pointers whose entries point to the
 *              - installed names in the given hash table
 */

char **SC_dump_hash(tab, patt, sort)
   HASHTAB *tab;
   char *patt;
   int sort;
   {return(_SC_dump_hash(tab, patt, NULL, sort));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* SC_HASH_DUMP - return an array of pointers whose entries point to the
 *              - installed names in the given hash table and
 *              - are alphabetically ordered (by strcmp)
 */

char **SC_hash_dump(tab, patt)
   HASHTAB *tab;
   char *patt;
   {return(_SC_dump_hash(tab, patt, NULL, TRUE));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* DHASH - debugging printout of names in hash table */

void dhash(tab, patt)
   HASHTAB *tab;
   char *patt;
   {int i, n;
    char **lines;

    lines = _SC_dump_hash(tab, patt, NULL, TRUE);

    if (lines != NULL)
       {n     = SC_arrlen(lines)/sizeof(char *);
	for (i = 0; i < n; i++)
	    {PRINT(stdout, "%4d\t%s\n", i, lines[i]);};

	SFREE(lines);};

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
