#if !defined(lint) && !defined(__INSIGHT__)
static char sos__rcsid[] = "config.c,v 1.1.1.1 1995/06/16 17:42:40 seth Exp";
static char sos__copyright[] = "Copyright (c) 1994, 1995 SOS Corporation";
static char sos__contact[] = "SOS Corporation <sos-info@soscorp.com> +1 800 SOS UNIX";
#endif /* not lint */

/*
 * ++Copyright Released Product++
 *
 * Copyright (c) 1994, 1995 Sources of Supply Corporation ("SOS").
 * All rights reserved.
 *
 * The SOS Released Product License Agreement specifies the terms and
 * conditions for redistribution.  You may find the License Agreement
 * in the file LICENSE.
 *
 * SOS Corporation
 * 461 5th Ave.; 16th floor
 * New York, NY 10017
 *
 * +1 800 SOS UNIX
 * <sos-info@soscorp.com>
 *
 * --Copyright Released Product--
 */

/*
 * General config-file reader.
 */

#include "sos.h"



/* 
 * These defs should remain local to the config module.
 */

/* #define CONFIGBUFSIZ (MIN((MAXPATHLEN+2), 2048)) */
#define CONFIGBUFSIZ (MAXPATHLEN+2)
static char config_buf[CONFIGBUFSIZ];

/* I like these definitions (more accurate than 0 and 1) */
#define FALSE 0
#define TRUE (!FALSE)


/* 
 * Dynamically allocate and zero key structure.
 */
static sos_config_key_t *
sos_config_allocate_key()
{

  sos_config_key_t *new;

  if ((new = (sos_config_key_t *) malloc(sizeof(sos_config_key_t)))
      == (sos_config_key_t *) NULL)
    /*
     * Let's leave it to the caller to figure out that something went wrong
     * just pass up the error.
     */
    return (NULL);

  new->key = (char *)NULL;
  new->current = (sos_config_value_t *) NULL;
  new->first = (sos_config_value_t *) NULL;
  new->last = (sos_config_value_t *) NULL;
  new->no_direction_yet = TRUE;
  new->num_values = 0;

  new->next = (sos_config_key_t *) NULL;

  return (new);
}



/* 
 * Dynamically allocate and zero value structure.
 */
static sos_config_value_t *
sos_config_allocate_value()
{

  sos_config_value_t *new;

  if ((new = (sos_config_value_t *) malloc(sizeof(sos_config_value_t)))
      == (sos_config_value_t *) NULL)
    /*
     * Let's leave it to the caller to figure out that something went wrong
     * just pass up the error.
     */
    return (NULL);

  new->value = (char *)NULL;

  new->next = (sos_config_value_t *) NULL;
  new->prev = (sos_config_value_t *) NULL;

  return (new);
}



/*
 * Add key into config file structure
 */
static sos_config_key_t *
insert_key(sos_config_key_t *config, sos_config_key key)
{
  sos_config_key_t *new;

  if ((new = sos_config_allocate_key()) == (sos_config_key_t *) NULL)
    /*
     * Pass up the error.  Make sure that caller knows to do the same
     */
    return (NULL);

  if ((new->key = (sos_config_key) malloc(strlen(key) + 1)) ==
      (sos_config_key) NULL)
    return (NULL);

  memcpy(new->key, key, strlen(key) + 1);

  /* 
   * Link it up to the key list (stack).
   */
  new->next = config;
  config = new;

  return config;
}



static sos_config_key_t *
find_key(sos_config_key_t *config, sos_config_key key)
{

  for (; config; config = config->next)
    if (SOS_STREQ(config->key, key))
      break;

  return (config);
}



/*
 * `config' is the main pointer to the whole kit and caboodle. 
 * 
 * Insert `value' into the config structure in the `key' bin.
 * If bin doesn't exist, insert it.
 *
 * return `config' since this function may well alter its value.
 */
static sos_config_key_t *
insert_value(sos_config_key_t *config,
	     const sos_config_key key,
	     const sos_config_value value)
{
  sos_config_key_t *bin;	/* Keys act like hash bins and I need a ident */
  sos_config_value_t *new;	/* new value pointer */

  if (!(bin = find_key(config, key)))
    {
      if ((config = insert_key(config, key)) == (sos_config_key_t *) NULL)
	return (NULL);
      bin = config;
    }

  if (!(new = sos_config_allocate_value()))
    return (NULL);

  if ((new->value = (sos_config_value) malloc(strlen(value) + 1)) ==
      (sos_config_value) NULL)
    return (NULL);

  /*
   * I know memcpy() is better, but strcpy will always find the EOS
   */
  (void)strcpy(new->value, value);

  /*
   * Queue up new value structure. 
   */
  if (!bin->first)
    {
      /*
       * This bin has never had a value installed. Initialize the key structure
       * pointers to the new value. 
       */
      bin->current = new;
      bin->first = new;
      bin->last = new;
    }
  else
    {

      bin->last->next = new;
      new->prev = bin->last;
      bin->last = new;
    }

  bin->num_values++;		/* Count the values */

  return (config);
}



/* 
 * Open and read the specified config file <filename>
 * There is no intelligence for parsing out unknown keywords, only syntatic
 * errors can be caught
 *
 * ConfigH is the handle on the created dictionary.
 * Config handle is returned.
 */
sos_config_t
sos_config_read(char *filename)
{
  FILE *Config;			/* The config file we are opening */
  int config_line_count = 0;	/* Used for nice error messages */
  sos_config_t ConfigH = NULL;	/* Config file handle. Returned */
  sos_config_key_t *temp;	/* Used for good error checking */

  if ( ( ConfigH = (sos_config_t)malloc(sizeof *ConfigH)) == NULL )
    {
      return (NULL);
    };

  memset(ConfigH->filename, (char)0, MAXPATHLEN);
  ConfigH->key_list = NULL;
  
  /*
   * Check if filename is qualified.  If not prepend .
   */
  if (*filename != '/' && *filename != '.')
    {
      sprintf(ConfigH->filename, "./%s", filename);
    }
  else
    {
      sprintf(ConfigH->filename, "%s", filename);
    }
    
  


  /*
   * Check access rights
   */
  if (access(ConfigH->filename, R_OK))
    {
      return NULL;
    }


  /*
   * Go ahad and open. NB This command should never fail.
   */
  if ((Config = fopen(ConfigH->filename, "r")) == (FILE *) NULL)
    {
      /* 
       * Of course this error should never happen, considering the access().
       */
      return NULL;
    }

  memset(config_buf, (char)NULL, CONFIGBUFSIZ);

  while (fgets(config_buf, CONFIGBUFSIZ, Config))
    {
      char *value;		/* Will parse out the value portion of the input line */
      char *key;		/* Useless but makes the code slightly more readable */

      config_line_count++;	/* NB this will start the count at 1 */

      /* 
       * Strip comments and blank lines
       * Left margin-abutting white space indicates a comment.
       */
      if (*config_buf == '#' ||
	  *config_buf == ';' ||
	  strspn(config_buf, SOS_WHITESPACE) )
	continue;

      /*
       * Temporarilly use `value' to strip off any NEWLINES.  You should
       * really consider its name tmp
       */
      if ((value = strchr(config_buf, '\n')) != (char *)NULL)
	*value = '\0';


      if ((value = strchr(config_buf, ' ')) == (char *)NULL)
	{
	  value = config_buf + strlen(config_buf);
	}
      else
	{
	  *value++ = '\0';
	}

      /*
       * Now it makes sense to use `key' (instead of config_buf)
       */
      key = config_buf;

      /*
       * Go ahead and pass these values of `key' and `value'. They will
       * get copied to permenent storage by the insertion routines.
       */
      temp = insert_value(ConfigH->key_list, key, value);
      if (temp && temp != ConfigH->key_list)
	/*
	 * Config structure got set/reset during this call so 
	 */
	ConfigH->key_list = temp;

    }				/* End of read loop */

  fclose(Config);

  return ConfigH;

}				/* End of sos_config_read() */




/* 
 * Get the next value of `key' from `config', moving in `direction', and
 * with the specified `semantic'
 * 
 * This function will oftern return NULL as normal, expected return value
 * (eg: if it is called with a LINEAR semantic and it already at the end 
 * of the list). NULL should *not* be considered an error.
 */

sos_config_value
sos_config_getnext(sos_config_t config,
		   sos_config_key key,
		   int direction,
		   int semantic)
{
  sos_config_key_t *bin;	/* Keys are like hash bins. Dont buy it do you? */
  sos_config_value_t *ret;	/* The structure containing the return value. */
  sos_config_key_t *config_key_list;
  
  if (config == NULL)
    return (NULL);

  config_key_list = config->key_list;

  ret = (sos_config_value_t *) NULL;

  if (!(bin = find_key(config_key_list, key)))
    return (NULL);

  if (bin->no_direction_yet)
    {
      /*
       * Initialize the direction of the `value' traversal
       */
      if (direction == SOS_CONFIG_REVERSE)
	bin->current = bin->last;
      else
	/* 
	 * This heavily favors moving in the forward direction, but that's 
	 * probably what most folks want.
	 */
	bin->current = bin->first;

      bin->no_direction_yet = FALSE;
    }

  ret = bin->current;

  /* 
   * If callee asks for a static read, return before updating the `current'
   * pointer
   */
  if (semantic == SOS_CONFIG_STATIC)
    {
      if (ret)
	return (ret->value);
      else
	return NULL;
    }

  /*
   * Update the `current' pointer based on the callee's desired direction
   * and semantic
   */
  if (ret)
    switch (direction)
      {
      case SOS_CONFIG_NULL:
	/* 
	 * If a client doesn't really know what it wants (thus uses NULL
	 * argument), advance the `current' pointer in the forward direction
	 */
      case SOS_CONFIG_FORWARD:
	bin->current = bin->current->next;
	break;

      case SOS_CONFIG_REVERSE:
	bin->current = bin->current->prev;
	break;

      default:
	break;
      }

  switch (semantic)
    {
    case SOS_CONFIG_NULL:
    case SOS_CONFIG_LINEAR:
      break;

    case SOS_CONFIG_AUTORESET:
      if (!ret)

    case SOS_CONFIG_CIRCULAR:
	if (!bin->current)
	  {
	    /* 
	     * If we have reached the end of the list and the callers wants
	     * circular sematics, reset the current appropriately
	     */
	    switch (direction)
	      {
	      case SOS_CONFIG_NULL:
	      case SOS_CONFIG_FORWARD:
		bin->current = bin->first;
		break;

	      case SOS_CONFIG_REVERSE:
		bin->current = bin->last;
		break;

	      default:
		break;
	      }
	  }
    }

  if (ret)
    return (ret->value);
  else
    return (NULL);
}


void
sos_config_print(sos_config_t config)

/* 
 * Print out the config structure
 */
{
  sos_config_value_t *value;
  sos_config_key_t *key_list;
  
  printf("************************************\n");
  printf("              CONFIG                \n");

  printf("filename: %s\n\t********\n", config->filename);

  for (key_list = config->key_list; key_list; key_list = key_list->next)
    {
      printf("%s: ", key_list->key);
      for (value = key_list->first; value; value = value->next)
	printf("**%s** ", value->value);
      printf("\n");
    }
  printf("************************************\n\n");
}


void 
sos_config_destroy_list(sos_config_t config)
{
  sos_config_key_t *k;
  sos_config_value_t *config_value;
  sos_config_key_t *key_list;
  sos_config_value_t *v;

  key_list = config->key_list;
  while (key_list)
    {
      memset(key_list->key, (char)0, strlen(key_list->key));
      free (key_list->key);

      config_value=key_list->first; 
      while ( config_value )
	{
	  memset (config_value->value, (char)0, strlen(config_value->value));
	  free (config_value->value);

	  v = config_value;
	  config_value = config_value->next;
	  free (v);
	}
      
      k = key_list;
      key_list = key_list->next;
      free (k);
    }
  
  memset(config->filename, (char)0, strlen(config->filename));
  free(config);
  return;
}


/*
 * Reload the config file pointed at by config from file name.
 * If filename is NULL, use the file name generated from the previous call
 * to sos_config_read.
 */
sos_config_t
sos_config_reload(sos_config_t config, char *filename)
{
  char *f;
  int need_free=0;

  if ( !config && !filename )
    return (NULL);

  if ( filename )
    {
      f = filename;
    }
  else
    {
      f = strdup(config->filename);
      need_free++;
    }
      
  if ( config)
    {
      sos_config_destroy_list(config);
    }

  config= sos_config_read(f);

  if ( need_free ) 
    {
      memset(f, (char)0, strlen(f));
      free(f);
    }

  return config;
}
