/* Copyright (C) 1993,1994 by the author(s).
 
 This software is published in the hope that it will be useful, but
 WITHOUT ANY WARRANTY for any part of this software to work correctly
 or as described in the manuals. See the ShapeTools Public License
 for details.

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * ShapeTools/shape program - hash.c
 *
 * Author: Axel Mahler (Axel.Mahler@cs.tu-berlin.de)
 *
 * $Header: hash.c[8.0] Wed Jul  7 18:32:58 1993 axel@cs.tu-berlin.de frozen $
 */
#ifndef lint
static char *AtFSid = "$Header: hash.c[8.0] Wed Jul  7 18:32:58 1993 axel@cs.tu-berlin.de frozen $";
#endif

#include "shape.h"

#define HASHSIZE 257

struct hash
{
  unsigned short visited;
  unsigned short kind_of_substitution; /* see shape.h  */
  char *name;
  char *entry;
  struct linked_list *add_entries;
  struct hash *next;
};

LOCAL struct hash hashTable[HASHSIZE];

EXPORT int hashval(name, tabsize)
     char *name;
     int tabsize;
{
  register int hash = 0, i = 0;

  if (!(name && *name))
    return 0;
  hash = name[0];
  for(i = 1; name[i] != '\0'; i++)
    hash = ((hash * 32) + name[i]) % tabsize;
  return hash;
} /* end hashval */


EXPORT void addHash (name, value, subst_kind)
     char *name;
     char *value;
     int  subst_kind;
{
  int  hashValue = hashval (name, HASHSIZE);
  register struct hash *current;
  Bool current_is_new = TRUE;
  
  /*
   * Try to find existing entry for name in hash table
   */

  current = &hashTable[hashValue];

  /*
   * since additive macro defs are stored in any case in a special
   * entry of the struct hash structure, there is no need to protect
   * it from redefintions in the Shapefile
   */
  if (subst_kind & APPEND)
    subst_kind &= ~FROM_CMDLINE;

  if (current->name) {
    while (current) {
      if (!strcmp (current->name, name)) /* found it */ { 
	current_is_new = FALSE;
	break;
      }
      else if (current->next) {
	current = current->next;
      }
      else {
	current->next = (struct hash *)check_malloc (sizeof (struct hash));
	current->next->entry = NULL;
	current->next->add_entries = NULL;
	current->next->next = NULL;
	current->next->name = NULL;
	current->next->visited = 0;
	current->next->kind_of_substitution = 0;
	current = current->next;
	break;
      }
    }
  }

  /*
   * "current" points to the correct (correctly linked) entry
   * in the hash table.
   * Now, see what needs to be done...
   */
    
  if (current_is_new) {
    if (subst_kind & APPEND) {
      current->add_entries = 
	(struct linked_list *)check_malloc (sizeof (struct linked_list));
      current->add_entries->string = check_strdup (value);
      current->add_entries->nextstring = NULL;
    }
    else
      current->entry = check_strdup (value);
    current->name = check_strdup (name);
    current->kind_of_substitution = subst_kind;
    return;
  }

  /*
   * We have to deal with a redefinition or definition
   * enhancement case (+=). This is a bit more complicated
   * as this interacts with the macro definition semantics.
   */

  if (subst_kind & APPEND)	{
    struct linked_list *lp, *prev_lp;

    lp = prev_lp = current->add_entries;
    while (lp) {
      lp = lp->nextstring;
      if (lp) prev_lp = lp;
    }
    if (!prev_lp) {
      current->add_entries = 
	(struct linked_list *)check_malloc (sizeof (struct linked_list));
      current->add_entries->string = check_strdup (value);
      current->add_entries->nextstring = NULL;
    }
    else {
      prev_lp->nextstring = 
	(struct linked_list *)check_malloc (sizeof (struct linked_list));
      prev_lp->nextstring->string = check_strdup (value);
      prev_lp->nextstring->nextstring = NULL;
    }
    return;
  }

  if (subst_kind & FROM_CMDLINE) { /* almost always override existing defs */
    if (current->entry)
      current->entry = check_realloc (current->entry, 
				      strlen(value) + sizeof(char));
    else
      current->entry = check_malloc (strlen(value) + sizeof(char));
    strcpy (current->entry, value);
    current->kind_of_substitution = subst_kind;
    return;
  }

  if ((current->kind_of_substitution & FROM_CMDLINE) &&
      !(current->kind_of_substitution & APPEND))
    return;

  if (current->entry)
    current->entry = check_realloc (current->entry, 
				    strlen(value) + sizeof(char));
  else
    current->entry = check_malloc (strlen(value) + sizeof(char));
  strcpy (current->entry, value);
  return;
  
} /* end addHash */

#define LASTNAME_LEN 128
LOCAL struct hash *current_hash;
LOCAL char lastname[LASTNAME_LEN], *lastentry = (char *)NIL;

EXPORT char *getHash (name)
     char *name;
{
  int  hashValue = hashval (name, HASHSIZE);

  if (!name || !*name)
    return NIL;

  if (name && !strcmp (lastname ? lastname : "", name))
    return lastentry;
  else 
    strcpy (lastname, name);

  if (!hashTable[hashValue].name) { /* nothing found */
    lastentry = NIL;
    return lastentry;
  }

  current_hash = &hashTable[hashValue];
  while (current_hash) {
    register struct linked_list *lp;

    if (!strcmp (current_hash->name, name)) { /* found it */
      lastentry = current_hash->entry;
      if (!lastentry) {
	lastentry = check_malloc (16); /* just something malloc'ed */
	*lastentry = '\0';
      }
      lp = current_hash->add_entries;
      while (lp) {
	if (lp->string && *lp->string) {
	  lastentry = check_realloc (lastentry, 
				     strlen (lastentry) + 
				     strlen (lp->string) +
				     2);
	  (void) strcat (lastentry, *lastentry ? " " : "");
	  (void) strcat (lastentry, lp->string);
	  free (lp->string);
	}
	{
	  struct linked_list *old_lp = lp;
	  lp = lp->nextstring;
	  free (old_lp);
	}
      }
      current_hash->entry = lastentry;
      if (current_hash->add_entries) current_hash->add_entries = NULL;

      current_hash->visited = TRUE;
      if (current_hash->kind_of_substitution & ONCE) {
	char *expanded_entry;
	struct hash *cur_hash_bak = current_hash;
	char lastname_bak[LASTNAME_LEN], 
	     *lastentry_bak = lastentry;
	int cmd_subst_flg = no_comm_subst;

	(void) strcpy (lastname_bak, lastname);
	no_comm_subst = FALSE;
	expanded_entry = check_strdup (expandmacro (lastentry));
	no_comm_subst = cmd_subst_flg;
	(void) strcpy (lastname, lastname_bak);
	current_hash = cur_hash_bak;
	lastentry = lastentry_bak;

	free (lastentry);
	lastentry = expanded_entry;
	current_hash->entry = lastentry;
	current_hash->kind_of_substitution &= ~ONCE;
      }
      return lastentry;
    }
    current_hash = current_hash->next;
  }
  lastentry = NIL;
  return lastentry;
}

EXPORT Bool visitedHash (name) char *name; {
  if (!name || !lastname)
    return FALSE;
  return ((name && !strcmp (lastname, name)) && 
	  lastentry && current_hash->visited);
}

EXPORT void clearHashVisited (name) char *name; {
  if (!name || !lastname)
    return;
  if ((name && !strcmp (lastname, name)) && lastentry)
    current_hash->visited = FALSE;
  return;
}
     
EXPORT void dump(fd)
     FILE *fd;
{
  register int i;
  register struct hash *current;
  
  fprintf(fd,"# Macros:\n");

  for(i = 0; i < HASHSIZE; i++)
    {
      if (hashTable[i].name)
	{
	  current = &hashTable[i];
	  while(current != (struct hash *) NIL)
	    {

	      if((current->name[0] != '$') &&
		 (current->name[0] != '*') &&
		 (current->name[0] != '+') &&
		 (current->name[0] != '<') &&
		 (current->name[0] != '?') &&
		 (current->name[0] != '@'))
		{
		  if(fd != stdout)
		    {
		      fprintf(fd,"%s = %s\n", current->name, current->entry);
		    }
		  else
		    {
		      fprintf(fd,"\t%s = %s\n", current->name, current->entry);
		    }
		}
	      current = current->next;
	      
	    }
	}
    }
} /* end dump */
