/*
 * Title: General Utility Routines
 * Utilities for portability, string manipulation and datastructure management
 *
 * Copyright (C) 2001-2006 Aron Rubin
 * $Id: general_utils.c,v 1.2 2006/10/24 20:34:52 arubin Exp $
 */

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <sys/time.h>
#include <math.h>
#include "general_utils.h"


/**********************************************\

               Section: Time

\**********************************************/

/* ------------------------------
   Function: get_current_time
     Get the highest resolution value for the current time that the system will
     report.

   Returns:
     the current time in seconds
   ------------------------------ */
double get_current_time() {
  double current = 0;
#ifndef __USE_POSIX
  SYSTEMTIME st;
  FILETIME tv;
  ULARGE_INTEGER li;

  GetSystemTime( &st );
  current = 1e-6*(double)st.wMilliseconds;
  st.wMilliseconds = 0;
  SystemTimeToFileTime( &st, &tv );
  memcpy( &li, &tv, sizeof(ULARGE_INTEGER) );
  current += (double)li.QuadPart;

#else
   struct timeval tv;

   gettimeofday( &tv, NULL );
   current = (double)tv.tv_sec + 1e-6*(double)tv.tv_usec;
#endif

  return( current );
}


/**********************************************\

  Section: String Manipulation and Tokenizing

\**********************************************/

/* ------------------------------
   Function: get_tok_quote
     Split the next token from a string, _str_, in-place using one or more
     characters in the set, _delim_, as the delimiting characters. This function
     will not split tokens within quotes, which distinguishes it from strtok_r.

   Parameters:
     str      - initially the string to be modified in-place, NULL after
     delim    - a set of characters used in any combination as delimiters
     save_ptr - a pointer to the state, should be considered opaque

   Returns:
     the next token in the stream, or NULL if no tokens exist
   ------------------------------ */
char *get_tok_quote( char *str, char *delim, char **save_ptr ) {
  char *ptr;

  if( !str ) {
    if( !*save_ptr )
      return( NULL );
    str = *save_ptr;
  }

  while( *str && strchr( delim, *str ) )
    ++str;

  if( !*str )
    return( NULL );

  ptr = str;

  while( *ptr && !strchr( delim, *ptr ) ) {
    if( *ptr == '"' ) {
      ++ptr;
      while( *ptr && *ptr != '"' ) {
	if( *(ptr + 1) && *ptr == '\\' )
	  ++ptr;
	++ptr;
      }
    }
    ++ptr;
  }

  *save_ptr = *ptr ? ptr + 1 : ptr;
  *ptr = '\0';

  return( str );
}

/* ------------------------------
   Function: c_strndup
     Duplicate (allocate a copy) no more than _len_ bytes of a string, _str_,
     always including a terminating '\0' character.

   Parameters:
     str - the string to be duplicated
     len - the maximum length of the resulting string not including
           not inclding the terminating '\0' character.

   Returns:
     a newly allocated string which is a duplicate of _str_, or NULL on
     allocation failure.
   ------------------------------ */
char *c_strndup( char *str, size_t len ) {
  char *dest, *ptr, *max;

  dest = ptr = malloc( len + 1 );
  if( !dest )
    return( NULL );
  max = ptr + len + 1;

  while( *str && ptr < max ) {
    *ptr = *str;
    ++ptr;
    ++str;
  }
  *ptr = '\0';

  if( *str )
    dest = realloc( dest, ptr - dest );

  return( dest );
}

/* ------------------------------
   Function: c_strtok_r
     Split the next token from a string, _str_, in-place using one or more
     characters in the set, _delim_, as the delimiting characters. This is
     implemented here for the cases where it is not in the native C library.

   Parameters:
     str      - initially the string to be modified in-place, NULL after
     delim    - a set of characters used in any combination as delimiters
     save_ptr - a pointer to the state, should be considered opaque

   Returns:
     the next token in the stream, or NULL if no tokens exist
   ------------------------------ */
char *c_strtok_r( char *str, char *delim, char **save_ptr ) {
  char *ptr;

  if( !str ) {
    if( !*save_ptr || !**save_ptr )
      return( NULL );
    str = *save_ptr;
  }

  while( *str && strchr( delim, *str ) )
    ++str;

  if( !*str )
    return( NULL );

  ptr = str;

  while( *ptr && !strchr( delim, *ptr ) )
    ++ptr;

  *save_ptr = *ptr ? ptr + 1 : ptr;
  *ptr = '\0';

  return( str );
}

char *c_strtok_r0( char *str, char *delim, char **save_ptr ) {
  char *ptr;

  if( !str )
    str = *save_ptr;

  while( *str && strchr( delim, *str ) )
    ++str;

  ptr = NULL;
  if( *str ) {
    ptr = str++;
    while( *str )
      if( strchr( delim, *str++ ) ) {
	str[-1] = '\0';
	break;
      }
    *save_ptr = str;
  }

  return( ptr );
}

/* ------------------------------
   Function: c_strtok_nd (string tokenize nondestructive)
     Split the next token from a string, _str_, non-destructively using one or
     more characters in the set, _delim_, as the delimiting characters.  The
     string pointed to by save_ptr holds the character immediately following the
     token returned.

   Parameters:
     str      - initially the string to be modified in-place, NULL after
     delim    - a set of characters used in any combination as delimiters
     save_ptr - parsing state save, should be considered opaque

   Returns:
     the next token in the stream, or NULL if no tokens exist
   ------------------------------ */
char *c_strtok_nd( char *str, char *delim, char **save_ptr ) {
  char *ptr;

  if( !str ) {
    if( !*save_ptr )
      return( NULL );
    str = *save_ptr;
  }

  while( *str && strchr( delim, *str ) )
    ++str;

  if( !*str )
    return( NULL );

  ptr = str;

  while( *ptr && !strchr( delim, *ptr ) )
    ++ptr;

  *save_ptr = *ptr ? ptr + 1 : ptr;

  return( str );
}


/* ------------------------------
   Function: strtrim (string trim)
     Remove the leading and trailing whitespace from a string, _str_,
     in-place.

   Parameters:
     str - the string to be modified in-place

   Returns - a pointer to the first character of the resulting string
   ------------------------------ */
char *strtrim( char *str ) {
  int len;

  if( !str )
    return( NULL );

  // chew front white space
  while( *str && isspace( (int)*str ) )
    ++str;

  // chew back white space
  len = strlen( str );
  while( len && isspace( (int)str[len - 1] ) ) {
    str[len - 1] = '\0';
    --len;
  }

  return( str );
}


/* ------------------------------
   Function: strtrimquote (string trim quotes)
     Remove the leading and trailing quotes from a string, _str_,
     in-place.

   Parameters:
     str - the string to be modified in-place

   Returns:
     a pointer to the first character of the resulting string
   ------------------------------ */
char *strtrimquote( char *str ) {
  int len;

  if( !str )
    return( NULL );

  /* chew front quote */
  if( *str && *str == '"' )
    ++str;

  /* chew back quote space */
  len = strlen( str );
  if( str[len - 1] == '"' )
    str[len - 1] = '\0';

  return( str );
}

/* ------------------------------
   Function: strtrimtag (string trim tag delimiters)
     Remove the leading and trailing XML tag delimiters from a string, _str_,
     in-place.

   Parameters:
     str - the string to be modified in-place

   Returns:
     a pointer to the first character of the resulting string
   ------------------------------ */
char *strtrimtag( char *str ) {
  int len;

  if( !str )
    return( NULL );

  /* chew open tag char */
  if( *str && *str == '<' )
    ++str;

  // chew close tag chars
  len = strlen( str );
  if( len > 0 && str[len - 1] == '>' ) {
    str[len - 1] = '\0';
    if( len > 1 && str[len - 2] == '/' ) {
      str[len - 2] = '\0';
    }
    
    // chew back white space
    len = strlen( str );
    while( len && isspace( (int)str[len - 1] ) ) {
      str[len - 1] = '\0';
      len = strlen( str );
    }
  }

  return( str );
}

/* ------------------------------
   Function: strpcpy (padded string copy)
     Copies no more than _len_ characters of a string, _src_, to a string
     buffer, _dst_. If _src_ is shorter than _len_ the character _pad_ is copied
     repeatedly until _dst_ has been set with _len_ characters.

   Parameters:
     dst - the string buffer to be set with the copied characters
     src - the string to copy from
     pad - the character to use to pad the desination buffer

   Returns:
     _dst_
   ------------------------------ */
char *strpcpy( char *dst, char *src, char pad, uint len ) {
  char *ptr = dst;

  // copy as much of src to dst as possible
  while( *src && len ) {
    *ptr = *src;
    ++src;
    ++ptr;
    --len;
  }

  // add padding
  while( len ) {
    *ptr = pad;
    ++ptr;
    --len;
  }

  // terminate dst
  *ptr = '\0';

  return( dst );
}

/* ------------------------------
   Function: strppcpy (pre-padded string copy)

     If a string, _src_, is shorter than _len_, the character _pad_ is copied
     into a string buffer, _dst_, repeatedly until the size of a string,
     _src_, remains. Copies no more than _len_ characters of a string, _src_,
     to the remaining portion of a string buffer, _dst_.

   Parameters:
     dst - the string buffer to be set with the copied characters
     src - the string to copy from
     pad - the character to use to pad the desination buffer

   Returns:
     _dst_
   ------------------------------ */
char *strppcpy( char *dst, char *src, char pad, uint len ) {
  uint slen = strlen( src );
  uint plen = len - slen;

  // if src is the same size as dst or larger than just copy
  if( plen <= 0 ) {
    memcpy( dst, src, len );
    dst[len] = '\0';

  // otherwise add padding to front;
  } else {
    char *ptr = dst;

    // add padding
    while( plen ) {
      *ptr = pad;
      ++ptr;
      --plen;
    }

    // copy as much of src to dst as possible
    while( *src && slen ) {
      *ptr = *src;
      ++src;
      ++ptr;
      --len;
    }

    // terminate dst
    *ptr = '\0';
  }

  return( dst );
}

/* ------------------------------
   Function: strflip (string flip)
     Reverse the order of characters in a string in-place.

   Parameters:
     str - a string to be flipped

   Returns:
     _str_
   ------------------------------ */
char *strflip( char *str ) {
  char *ptr = str, *end = str + strlen(str) - 1, ch;

  if( !*ptr )
    return( str );

  while( ptr < end ) {
    ch = *ptr;
    *ptr = *end;
    *end = ch;
    ++ptr;
    --end;
  }

  return( str );
}

/* ------------------------------
   Function: strrot (string rotate)
     Rotate the order of characters of a string.

   Parameters:
     dst	- a string buffer to hold the result
     src	- a string to be rotated
     offset	- how many places to shift each character
                  (may be negative)

   Returns:
     _dst_
   ------------------------------ */
char *strrot( char *dst, char *src, int offset ) {
  uint len = strlen( src ), rest;

  offset = offset%len;
  if( offset < 0 ) {
    rest = len + offset;
    memcpy( dst, src - offset, rest );
    memcpy( dst + rest, src, -offset );
  } else {
    rest = len - offset;
    memcpy( dst, src + rest, offset );
    memcpy( dst + offset, src, rest );
  }

  return( dst );
}

/* ------------------------------
   Function: strrotc (string rotate characters)
     Rotate in-place the value of characters of a string within an upper and
     lower bound.

   Parameters:
     str	- a string containing characters to be rotated
     upper	- an upper bound on the rotation values
     lower	- a lower bound on the rotation values
     offset	- an offset to shift the value of each character
           	  (may be negative)

   Returns:
     _str_
   ------------------------------ */
char *strrotc( char *str, char upper, char lower, int offset ) {
  char *ptr = str, delta = upper - lower;

  while( *ptr ) {
    *ptr = ((*ptr - lower + offset)%delta) + lower;
    ++ptr;
  }

  return( str );
}

/* ------------------------------
   Function: strinterleave (string interleave)
     Copy a character from each of two source strings, _src1_ and _src2_, in
     order into a string buffer, _dst_, thus interleaving them.

   Parameters:
     dst	- a string buffer to store the result
     src1	- a string to copy from
     src2	- a string to copy from

   Returns:
     _dst_
   ------------------------------ */
char *strinterleave( char *dst, char *src1, char *src2 ) {
  char *ptr = dst;

  while( *src1 && *src2 ) {
    *ptr = *src1;
    ++src1;
    ++ptr;
    *ptr = *src2;
    ++src2;
    ++ptr;
  }
  if( *src1 )
    strcpy( ptr, src1 );
  else if( *src2 )
    strcpy( ptr, src2 );
  else
    *ptr = '\0';

  return( dst );
}

/* ------------------------------
   Function: strcase (string case)
     Change the case (capitalization) of each character of a string, _str_,
     in-place according to one of four modes.

   Parameters:
     str	- a string to modify
     mode	- the mode of transformation
     shiftall	- whether to emulate the shift key modifier of qwerty

   Returns:
     _str_
   ------------------------------ */
char *strcase( char *str, int mode, int shiftall ) {
  static char fulllower[256], fullupper[256];
  static int init = 0;
  char *ptr = str;

  // set up conversion tables
  if( !init ) {
    int i = 0;
    char *punclower = "1'3457'908=,-./0123456789;;,=./2abcdefghijklmnopqrstuvwxyz[\\]6-`abcdefghijklmnopqrstuvwxyz[\\]`";
    char *puncupper = "!\"#$%&\"()*+<_>?)!@#$%^&*(::<+>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}^_~ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~";
    for( ; i < '!'; i++ )
      fulllower[i] = fullupper[i] = i;
    for( ; i <= '~'; i++ ) {
      fulllower[i] = punclower[i - '!'];
      fullupper[i] = puncupper[i - '!'];
    }
    for( ; i < 256; i++ )
      fulllower[i] = fullupper[i] = i;
  }

  switch( mode ) {
  case -1:
  case 0:
    if( !shiftall ) {
      while( *ptr ) {
	*ptr = tolower( *ptr );
	++ptr;
      }
    } else {
      while( *ptr ) {
	*ptr = fulllower[(int)*ptr];
	++ptr;
      }
    }
    break;
  case 1:
    if( !shiftall ) {
      while( *ptr ) {
	*ptr = toupper( *ptr );
	++ptr;
      }
    } else {
      while( *ptr ) {
	*ptr = fullupper[(int)*ptr];
	++ptr;
      }
    }
    break;
  case 2:
    if( !shiftall ) {
      while( *ptr ) {
	*ptr = toupper( *ptr );
	++ptr;
	if( *ptr ) {
	  *ptr = tolower( *ptr );
	  ++ptr;
	}
      }
    } else {
      while( *ptr ) {
	*ptr = fullupper[(int)*ptr];
	++ptr;
	if( *ptr ) {
	  *ptr = fulllower[(int)*ptr];
	  ++ptr;
	}
      }
    }
    break;
  case 3:
    if( !shiftall ) {
      while( *ptr ) {
	*ptr = tolower( *ptr );
	++ptr;
	if( *ptr ) {
	  *ptr = toupper( *ptr );
	  ++ptr;
	}
      }
    } else {
      while( *ptr ) {
	*ptr = fulllower[(int)*ptr];
	++ptr;
	if( *ptr ) {
	  *ptr = fullupper[(int)*ptr];
	  ++ptr;
	}
      }
    }
    break;
  }

  return( str );
}

/* ------------------------------
   Function: strrncmp (string reverse compare n)
     Compare two strings, _s1_ and _s2_, starting at the end. The end of a
     string should be no greater than _n_ characters in.

   Parameters:
     s1 	- a string to compare
     s2 	- a string to compare
     n  	- the maximum characters of a string to evaluate

   Returns:
     a value less than 0 if if the first differing character of s1 is less than
     that of s2, a value of 0 if all evaluated characters in s1 and s2 are
     equal, or a value greater than 0 if the first differing character of s1 is
     greater than that of s2.
   ------------------------------ */
int strrncmp( char *s1, char *s2, size_t n ) {
  char *p1 = s1, *p2 = s2;

  // get to the ends
  while( *p1 )
    ++p1;
  while( *p2 )
    ++p2;
  
  // check for zero length strings
  if( !(*s1 && *s2 && n) )
    return( *p1 - *p2 );

  // compare
  while( (--p1) > s1 && (--p2) > s2 && --n && *p1 == *p2 );
  return( *p1 - *p2 );
}

/* ------------------------------
   Function: strnchr (string character search n)
     Search through a string, _str_, for a character, _chr_, no further than _n_
     characters in.

   Parameters:
     str 	- a string to search through (haystack)
     chr 	- a character to search for (needle)
     n  	- the maximum characters of string to evaluate

   Returns:
     a pointer to the first occurance of _chr_ in _str_, or NULL if _chr_ does
     not occur in _str_.
   ------------------------------ */
char *strnchr( char *str, char chr, size_t n ) {
  char *end = str + n;
  while( str < end && *str ) {
    if( *str == chr )
      return( str );
    str++;
  }
  return( NULL );
}

/* ------------------------------
   Function: memspool (memory spool)
     Allocate and copy into a dynamically sized byte array. The allocated size
     of the byte array is assumed to be rounded up to the nearest block of 64
     bytes in order to reduce the number of system allocation calls needed.

   Parameters:
     spool	- an allocation of memory populated using the 64 byte
          	  ceiling method
     len	- a pointer to the amount of _spool_ used (in/out param)
     data	- bytes to add to _spool_
     n  	- the size (in bytes) of data

   Returns:
     an allocation of memory which is the concatication of _spool_ and _data_.
   ------------------------------ */
char *memspool( char *spool, uint *len, const void *data, const size_t n ) {
  int newlen, modlen;

  if( !spool )
    *len = 0;
  else if( !*len )
    spool = NULL;

  // if 
  if( !n )
    return( spool );

  newlen = *len + n;
  modlen = (newlen + (1 << 6)) >> 6;

  if( !*len || modlen > ((*len + (1 << 6)) >> 6) ) {
    if( spool )
      spool = realloc( spool, modlen << 6 );
    else
      spool = malloc( modlen << 6 );
  }

  memcpy( spool + *len, data, n );
  *len = newlen;

  return( spool );
}

/* ------------------------------
   Function: chrspool (character spool)
     Allocate and copy into a dynamically sized byte array. The allocated size
     of the byte array is assumed to be rounded up to the nearest block of 64
     bytes in order to reduce the number of system allocation calls needed.

   Parameters:
     spool	- an allocation of memory populated using the 64 byte
          	  ceiling method
     len	- a pointer to the amount of _spool_ used (in/out param)
     chr	- byte to add to _spool_

   Returns:
     an allocation of memory which is the concatication of _spool_ and _chr_.
   ------------------------------ */
char *chrspool( char *spool, uint *len, char chr ) {
  return( memspool( spool, len, &chr, 1 ) );
}

/* ------------------------------
   Function: strspool (string spool)
     Allocate and copy into a dynamically sized string. The allocated size of
     the string is assumed to be rounded up to the nearest block of 64 bytes in
     order to reduce the number of system allocation calls needed.

   Parameters:
     spool	- an allocation of memory populated using the 64 byte
          	  ceiling method
     len	- a pointer to the amount of _spool_ used (in/out param)
     str	- string to add to _spool_

   Returns:
     an allocation of memory which is the concatication of _spool_ and _str_.
   ------------------------------ */
char *strspool( char *spool, uint *len, const char *str ) {
  if( spool && *len && !spool[*len - 1] )
    --*len;
  return( memspool( spool, len, str, strlen( str ) + 1 ) );
}

/* ------------------------------
   Function: sstrjoin (spool string join)
     Join any number of strings together using joiner inbetween each source
     string. The joined string is spooled.

   Parameters:
     spool	- an allocation of memory populated using the 64 byte
          	  ceiling method
     len	- a pointer to the amount of _spool_ used (in/out param)
     joiner	- string to add inbetween each adjacent pair of
           	  concatinated strings
     ...	- strings to join together into _spool_

   Returns:
     an allocation of memory which is the concatication of _spool_ and the
     joined string.
   ------------------------------ */
char *sstrjoin( char *spool, uint *len, const char *joiner, ... ) {
  va_list lst;
  char *str;
  int joinerlen = strlen( joiner );

  va_start( lst, joiner );
  str = va_arg( lst, char * );
  if( str ) {
    spool = memspool( spool, len, str, strlen( str ) );
    while( (str = va_arg( lst, char * )) ) {
      spool = memspool( spool, len, joiner, joinerlen );
      spool = memspool( spool, len, str, strlen( str ) );
    }
  }
  spool = memspool( spool, len, "", 1 );
  return( spool );
}

/* ------------------------------
   Function: strjoin (string join)
     Join any number of strings together using joiner inbetween each source
     string.

   Parameters:
     joiner	- string to add inbetween each adjacent pair of
           	  concatinated strings
     ...	- strings to join together

   Returns:
     an allocation of memory which is the concatication of _spool_ and the
     joined string.
   ------------------------------ */
char *strjoin( const char *joiner, ... ) {
  va_list lst;
  char *spool = NULL, *str;
  uint len = 0, joinerlen = strlen( joiner );

  va_start( lst, joiner );
  str = va_arg( lst, char * );
  if( str ) {
    spool = memspool( spool, &len, str, strlen( str ) );
    while( (str = va_arg( lst, char * )) ) {
      spool = memspool( spool, &len, joiner, joinerlen );
      spool = memspool( spool, &len, str, strlen( str ) );
    }
  }
  spool = memspool( spool, &len, "", 1 );
  spool = realloc( spool, len );
  return( spool );
}

/* ------------------------------
   Function: sstrvjoin (spool string join array)
     Join any number of strings together using joiner inbetween each source
     string. The joined string is spooled.

   Parameters:
     spool	- an allocation of memory populated using the 64 byte
          	  ceiling method
     len	- a pointer to the amount of _spool_ used (in/out param)
     joiner	- string to add inbetween each adjacent pair of
           	  concatinated strings
     strs	- NULL terminated array of strings to join together into
         	  _spool_

   Returns:
     an allocation of memory which is the concatication of _spool_ and the
     joined string.
   ------------------------------ */
char *sstrvjoin( char *spool, uint *len, const char *joiner, char **strs ) {
  int i = 0, joinerlen = strlen( joiner );

  if( strs[i] ) {
    spool = memspool( spool, len, strs[i], strlen( strs[i] ) );
    while( strs[++i] ) {
      spool = memspool( spool, len, joiner, joinerlen );
      spool = memspool( spool, len, strs[i], strlen( strs[i] ) );
    }
  }
  spool = memspool( spool, len, "", 1 );
  return( spool );
}

/* ------------------------------
   Function: strvjoin (string join array)
     Join any number of strings together using joiner inbetween each source
     string.

   Parameters:
     joiner	- string to add inbetween each adjacent pair of
           	  concatinated strings
     strs	- NULL terminated array of strings to join together

   Returns:
     an allocation of memory which is the concatication of _spool_
     and the joined string.
   ------------------------------ */
char *strvjoin( const char *joiner, char **strs ) {
  char *spool;
  uint len = 0;

  spool = sstrvjoin( NULL, &len, joiner, strs );
  return( realloc( spool, len ) );
}


/**********************************************\

         Section: Simple XML parser

\**********************************************/

/* ------------------------------
   Function: sxml_unescape
     Convert XML escape sequences to there equivalent forms.

   Parameters:
     str       - string to convert
     slen      - the depth to look into the string
     append_eq - whether to append an equal sign to the output

   Returns:
     an allocation of memory which is the converted string
   ------------------------------ */
char *sxml_unescape( char *str, int slen, int append_eq ) {
  char *spool = NULL, *amp;
  int len = 0, dist, i;
  static char *entities[][2] = { {"amp", "&"},  {"quot", "\""}, {"apos", "'"},
				 {"lt", "<"}, {"gt", ">"} };

  if( !str )
    return( NULL );

  //printf( "\nsxml_unescape( %*s, %d, %d )\n", slen, str, slen, append_eq );
  while( *str && (amp = strnchr( str, '&', slen )) ) {
    dist = amp - str;
    spool = memspool( spool, &len, str, dist );
    str = amp + 1;
    slen -= dist + 1;
    if( (amp = strnchr( str, ';', slen )) ) {
      *amp = '\0';
      if( str[0] == '#' ) {
	char chr = (char)(str[1] == 'x' || str[1] == 'X' ?
			  strtol( str + 2, NULL, 16 ) :
			  strtol( str + 1, NULL, 0 ));
	spool = memspool( spool, &len, &chr, 1 );
      } else {
	for( i = 0; entities[i][0] && strcmp( entities[i][0], str ); i++ );
	if( entities[i][0] )
	  spool = memspool( spool, &len, entities[i][1], strlen(entities[i][1]) );
      }
      slen -= amp - str + 1;
      str = amp + 1;
    }
  }
  if( *str )
    spool = memspool( spool, &len, str, strlen( str ) );
  if( append_eq )
    spool = memspool( spool, &len, "=", 2 );
  else
    spool = memspool( spool, &len, "", 1 );
  //printf( "sxml_unescaped string %d \"%s\"\n", len, spool );
  spool = realloc( spool, len );
  return( spool );
}

char *sxml_spool_escaped( char *spool, uint *len, char *str ) {
  while( *str ) {
    switch( *str ) {
    case '&':
      spool = memspool( spool, len, "&amp;", 5 );
      break;
    case '"':
      spool = memspool( spool, len, "&quot;", 6 );
      break;
    case '\'':
      spool = memspool( spool, len, "&apos;", 6 );
      break;
    case '<':
      spool = memspool( spool, len, "&lt;", 4 );
      break;
    case '>':
      spool = memspool( spool, len, "&gt;", 4 );
      break;
    default:
      spool = memspool( spool, len, str, 1 );
      break;
    }
    str++;
  }

  return( spool );
}

/* ------------------------------
   Function: sxml_escape
     Replace XML sensative characters in an ASCII string with XML entity escape
     sequences.

   Parameters:
     str - a string to convert

   Returns:
     an allocation of memory which is the converted string
   ------------------------------ */
char *sxml_escape( char *str ) {
  char *spool = NULL;
  uint len = 0;
  spool = sxml_spool_escaped( NULL, &len, str );
  spool = memspool( spool, &len, "", 1 );
  return( realloc( spool, len ) );
}

/* ------------------------------
   Function: sxml_parse
     Iteratively parse XML strings and call the specified functions when the
     respective tokens are encountered. Incomplete tokens are stored in _spool_.

   Parameters:
     spool - incomplete tokens
     len   - length of the _spool_
     str   - an XML string to parse, this will be appended to _spool_ if exists
     slen  - the number of characters of _str_ to process
     start - the function to call when a start tag element is encountered
     end   - the function to call when an end tag element is encountered
     chars - the function to call when characters are found outside of a tag
     ud    - user supplied data which is passed to all callbacks

   Returns:
     a new spool of incomplete tokens. This should be passed into subsequent
     calls to sxml_parse for the same stream.

   See Also:
     <SXMLStartTagFunc>, <SXMLEndTagFunc>, <SXMLCharsFunc>
   ------------------------------ */
char *sxml_parse( char *spool, uint *len, char *str, size_t str_len,
		  SXMLStartTagFunc start, SXMLEndTagFunc end,
		  SXMLCharsFunc chars, void *ud ) {
  int i, j, quote = 0, backslash = 0, cdata = 0;
  char *ptr, *name, *attrs[128], *tag, *equals, *str2, *save_ptr, *strend;

  strend = str + str_len;

  while( str < strend ) {
    // if there is a tag waiting in the spool, try to collect up a whole
    if( spool && *len && *spool == '<' ) {
      // figure out the last quotation state and escape (backslash) states
      backslash = 0;
      quote = 0;
      ptr = spool;
      for( i = 0; i < *len; i++ ) {
	if( !backslash ) {
	  if( !quote && (ptr[i] == '"' || ptr[i] == '\'') )
	    quote = ptr[i];
	  else if( quote && ptr[i] == quote )
	    quote = 0;
	}
        if( backslash || ptr[i] == '\\' )
	  backslash = !backslash;
      }

      // attempt to run to the end of the tag with the new input
      do {
	if( !backslash ) {
	  if( !quote && (ptr[i] == '"' || ptr[i] == '\'') )
	    quote = ptr[i];
	  else if( quote && ptr[i] == quote )
	    quote = 0;
	}
        if( backslash || *str == '\\' )
	  backslash = !backslash;

	spool = memspool( spool, len, str, 1 );
	if( quote || *str != '>' )
	  str++;
      } while( str < strend && (quote || *str != '>') );
      if( str < strend ) {
	spool = memspool( spool, len, "", 1 );
	tag = strtrimtag( strtrim( spool ) );
	if( !tag || !*tag )
	  continue;
	name = strtok_r( tag, " \t\n", &save_ptr );
	if( !name || !*name )
	  continue;

	i = 0;
	while( i < 126 && (attrs[i] = get_tok_quote( NULL, " \t\n", &save_ptr )) ) {
	  if( (equals = strchr( attrs[i], '=' )) ) {
	    *equals = '\0';
	    equals++;
	    str2 = strtrimquote( strtrim( attrs[i] ) );
	    attrs[i] = sxml_unescape( str2, strlen( str2 ), 1 );
	    str2 = strtrimquote( strtrim( equals ) );
	    attrs[i + 1] = sxml_unescape( str2, strlen( str2 ), 0 );
	    i += 2;
	  } else {
	    str2 = strtrimquote( strtrim( attrs[i] ) );
	    attrs[i] = sxml_unescape( str2, strlen( str2 ), 0 );
	    i++;
	  }
	}
	attrs[i] = NULL;

	name = strtrim( name );
	if( *name == '/' ) {
	  //printf( "sxml: calling end: \"%s\"\n", name );
	  if( end )
	    end( name + 1, ud );
	} else if( start ) {
	  start( name, attrs, ud );
	  //printf( "sxml: calling start: \"%s\"\n", name );
	}
	if( i && attrs[i - 1][0] == '/' && attrs[i - 1][1] == '\0' && end )
	  end( name, ud );
	// free attributes
	for( j = 0; j < i; j++ )
	  free( attrs[j] );
	free( spool );
	spool = NULL;
	
	*len = 0;
	str++;
      }
    } else {
      while( str < strend && *str != '<' ) {
	spool = memspool( spool, len, str, 1 );
	str++;
      }
      if( spool && *len ) {
	spool = memspool( spool, len, "", 1 );
	//printf( "sxml: calling chars: \"%s\"\n", spool );
	if( chars ) {
	  str2 = sxml_unescape( spool, *len, 0 );
	  chars( str2, ud );
	  free( str2 );
	}
	free( spool );
	spool = NULL;
	*len = 0;
      }
      if( str < strend && *str == '<' ) {
	spool = memspool( spool, len, str, 1 );
	++str;
      }
    }
  }

  return( spool );
}

SXMLNsContext *sxml_ns_create( SXMLNsContext *context, char *nsname, char *uri,
			       SXMLStartTagFunc start, SXMLEndTagFunc end,
			       SXMLCharsFunc chars, void *ud ) {
  SXMLNsHandler *nshandler;
  int fresh_context = 0;
  void **entry = NULL;

  // fixme: add sanity checks

  if( !context ) {
    fresh_context = 1;
    context = c_new0( SXMLNsContext, 1 );
  }

  if( nsname ) {
    if( !context->handlers )
      hash_tree_new( 31 );
    entry = hash_tree_access( context->handlers, nsname, strlen( nsname ) + 1,
			      0, 1 );
    if( *entry ) {
      if( fresh_context )
	free( context );
      return( NULL );
    }
  }

  nshandler = c_new0( SXMLNsHandler, 1 );
  if( nsname )
    nshandler->nsname = strdup( nsname );
  if( uri )
    nshandler->uri = strdup( uri );
  nshandler->start = start;
  nshandler->end = end;
  nshandler->chars = chars;
  nshandler->user_data = ud;

  if( !context->default_ns || (!nsname && uri) ) {
    if( context->default_ns && !context->default_ns->nsname )
      free( context->default_ns );
    context->default_ns = nshandler;
  }

  if( entry )
    *entry = nshandler;
    
  return( context );
}

void sxml_ns_start( char *name, char *attrs[], void *ud ) {
  SXMLNsContext *context = (SXMLNsContext *)ud;

  if( !context )
    return( 1 );

  
}

void sxml_ns_end( char *name, void *ud ) {
  SXMLNsContext *context = (SXMLNsContext *)ud;

  if( !context )
    return( 1 );

  
}

void sxml_ns_chars( char *chars, void *ud ) {
  SXMLNsContext *context = (SXMLNsContext *)ud;

  if( !context )
    return( 1 );

  
}

int sxml_ns_parse( SXMLNsContext *context, char *str, size_t str_len ) {
  if( !context || !str || !str_len )
    return( 1 );

  context->spool = sxml_parse( context->spool, &context->spool_len, str,
			       str_len, sxml_ns_start, sxml_ns_end,
			       sxml_ns_chars, context );
  return( 0 );
}

/**********************************************\

         Section: Hash Table/Tree

\**********************************************/


unsigned int fnv_1a_hash( void *buf, size_t len, unsigned int hval ) {
  /* The following statements apply only to the function which they are in.
   * Please do not copyright this code.  This code is in the public domain.
   *
   * LANDON CURT NOLL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
   * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
   * EVENT SHALL LANDON CURT NOLL BE LIABLE FOR ANY SPECIAL, INDIRECT OR
   * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
   * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
   * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
   * PERFORMANCE OF THIS SOFTWARE.
   *
   * By:
   *	chongo <Landon Curt Noll> /\oo/		\
   *      http://www.isthe.com/chongo/
   *
   * Share and Enjoy!	:-)
   */
  unsigned char *bp = buf;
  unsigned char *be = bp + len;		/* end of buffer */

  // FNV-1a hash each octet in the buffer
  while( bp < be ) {
    // xor the bottom with the current octet
    hval ^= (unsigned int)*bp;
    bp++;
    hval += (hval<<1) + (hval<<4) + (hval<<7) + (hval<<8) + (hval<<24);
    //hval += (hval<<1) + (hval<<4) + (hval<<5) + (hval<<7) + (hval<<8) +
    //        (hval<<40);
  }

  return( hval );
}

/* ------------------------------
   Function: hash_tree_new
     Create a new hash tree with _fanout_ buckets in the top level.

   Parameters:
     fanout - the number of buckets in the top level of the tree

   Returns:
     an allocation of memory which is a new hash tree
   ------------------------------ */
HashTreeNode *hash_tree_new( size_t fanout ) {
  HashTreeNode *trunk = c_new0( HashTreeNode, 1 );

  trunk->size = c_prime_ceiling( fanout );
  trunk->o.nodes = c_new0( HashTreeNode *, trunk->size );

  return( trunk );
}

HashTreeNode *hash_tree_node_new( void *key, size_t size ) {
  HashTreeNode *node = c_new0( HashTreeNode, 1 );

  if( key ) {
    node->size = size;
    if( size )
      node->key = c_dup( key, char, size );
  } else {
    node->size = c_prime_ceiling( size );
    node->o.nodes = c_new0( HashTreeNode *, node->size );
  }

  return( node );
}

/* ------------------------------
   Function: hash_tree_access
     Operate on a hash tree. Possible operations are lookup, create, delete.

   Parameters:
     trunk   - a trunk of a hash tree to operate on
     key     - a key to use to identify an entry
     size    - the size of the key
     prehash - initial hash of this key
     mode    - selects the operation to perform 0=lookup, 1=create, -1=delete

   Returns:
     When performing a create, a pointer to the value holder associated with the
     supplied key is always returned. When performing a lookup a pointer to the
     value holder associated with the supplied key is returned if it was
     previously created, NULL otherwise. When performing a delete, NULL is
     always returned.
   ------------------------------ */
void **hash_tree_access( HashTreeNode *trunk, void *key, size_t size,
			 uint prehash, int mode ) {
  HashTreeNode *branch, *bp;
  unsigned int hash;
  int cmp = 0;

  if( !trunk )
    return( NULL );

  if( !prehash )
    hash = fnv_1a_hash( key, size, 0 )%trunk->size;
  else
    hash = prehash%trunk->size;

  if( !trunk->o.nodes[hash] || trunk->o.nodes[hash]->size < size ||
      (trunk->o.nodes[hash]->size == size &&
       (cmp = memcmp( trunk->o.nodes[hash]->key, key, size )) < 0) ) {
    if( mode <= 0 )
      return( NULL );
    branch = hash_tree_node_new( key, size );
    branch->next = trunk->o.nodes[hash];
    trunk->o.nodes[hash] = branch;
  } else if( trunk->o.nodes[hash]->size == size && cmp == 0 ) {
    branch = trunk->o.nodes[hash];
    if( mode < 0 ) {
      trunk->o.nodes[hash] = branch->next;
      if( branch->key )
	free( branch->key );
      free( branch );
      return( NULL );
    }
  } else {
    bp = trunk->o.nodes[hash];
    while( bp->next && (bp->next->size > size ||
			(bp->next->size == size &&
			 (cmp = memcmp( bp->next->key, key, size )) > 0)) )
      bp = bp->next;
    if( bp->next && bp->next->size == size && cmp == 0 ) {
      branch = bp->next;
      if( mode < 0 ) {
	bp->next = branch->next;
	if( branch->key )
	  free( branch->key );
	free( branch );
	return( NULL );
      }
    } else {
      if( mode <= 0 )
	return( NULL );
      branch = hash_tree_node_new( key, size );
      branch->next = bp->next;
      bp->next = branch;
    }
  }

  return( &(branch->o.data) );
}

/* ------------------------------
   Function: hash_tree_direct_access
     Operate on a hash tree where keys are limited to machine word
     size. Possible operations are lookup, create, delete.

   Parameters:
     trunk   - a trunk of a hash tree to operate on
     key     - a key to use to identify an entry
     mode    - selects the operation to perform 0=lookup, 1=create, -1=delete

   Returns:
     When performing a create, a pointer to the value holder associated with the
     supplied key is always returned. When performing a lookup a pointer to the
     value holder associated with the supplied key is returned if it was
     previously created, NULL otherwise. When performing a delete, NULL is
     always returned.
   ------------------------------ */
void **hash_tree_direct_access( HashTreeNode *trunk, void *key, int mode ) {
  HashTreeNode *branch, *bp;
  size_t hash;

  if( !trunk )
    return( NULL );

  hash = ((unsigned long)key)%trunk->size;

  if( !trunk->o.nodes[hash] || trunk->o.nodes[hash]->key < key ) {
    if( mode <= 0 )
      return( NULL );
    branch = c_new0( HashTreeNode, 1 );
    branch->key = key;
    branch->next = trunk->o.nodes[hash];
    trunk->o.nodes[hash] = branch;
  } else if( trunk->o.nodes[hash]->key == key ) {
    branch = trunk->o.nodes[hash];
    if( mode < 0 ) {
      trunk->o.nodes[hash] = branch->next;
      free( branch );
      return( NULL );
    }
  } else {
    bp = trunk->o.nodes[hash];
    while( bp->next && bp->next->key > key )
      bp = bp->next;
    if( bp->next && bp->next->key == key ) {
      branch = bp->next;
      if( mode < 0 ) {
	bp->next = branch->next;
	free( branch );
	return( NULL );
      }
    } else {
      if( mode <= 0 )
	return( NULL );
      branch = c_new0( HashTreeNode, 1 );
      branch->key = key;
      branch->next = bp->next;
      bp->next = branch;
    }
  }

  return( &(branch->o.data) );
}

/*
void hash_tree_remove( HashTreeNode *trunk, void *key, size_t size ) {
  HashTreeNode *branch, *bp;
  unsigned int hash = 0;
  int cmp;

  if( !trunk )
    return;

  hash = fnv_1a_hash( key, size, hash )%trunk->size;

  if( !trunk->o.nodes[hash] )
    return;

  cmp = memcmp( trunk->o.nodes[hash]->key, key, size );
  if( cmp == 0 ) {
    branch = trunk->o.nodes[hash];
    trunk->o.nodes[hash] = branch->next;
    if( branch->key )
      free( branch->key );
    free( branch );
  } else if( cmp > 0 ) {
    bp = trunk->o.nodes[hash];
    while( bp->next && (cmp = memcmp( bp->next->key, key, size )) > 0 )
      bp = bp->next;
    if( bp->next && cmp == 0 ) {
      branch = bp->next;
      bp->next = branch->next;
      if( branch->key )
	free( branch->key );
      free( branch );
    }
  }
}
*/

/* ------------------------------
   Function: hash_tree_destroy
     Free all memory associed with a hash tree.

   Parameters:
     trunk   - a trunk of a hash tree to operate on
   ------------------------------ */
void hash_tree_destroy( HashTreeNode *trunk ) {
  HashTreeNode *branch;
  size_t i;

  for( i = 0; i < trunk->size; i++ ) {
    while( trunk->o.nodes[i] ) {
      branch = trunk->o.nodes[i];
      trunk->o.nodes[i] = trunk->o.nodes[i]->next;
      if( branch->key && branch->size )
	free( branch->key );
      free( branch );
    }
  }
  if( trunk->key && trunk->size )
    free( trunk->key );
  free( trunk );
}

/* ------------------------------
   Function: hash_tree_remove
     Remove the hash tree entry associated with a specified key.

   Parameters:
     trunk   - a trunk of a hash tree to operate on
     key     - a key to use to identify an entry
     size    - the size of the key
   ------------------------------ */
void hash_tree_remove( HashTreeNode *trunk, void *key, size_t size ) {
  hash_tree_access( trunk, key, size, 0, -1 );
}

/* ------------------------------
   Function: hash_tree_lookup
     Lookup the hash tree entry associated with a specified key.

   Parameters:
     trunk - a trunk of a hash tree to operate on
     key   - a key to use to identify an entry
     size  - the size of the key

   Returns:
     If an entry associated with a specified key exists its value is returned,
     NULL otherwise.
   ------------------------------ */
void *hash_tree_lookup( HashTreeNode *trunk, void *key, size_t size ) {
  void **val = hash_tree_access( trunk, key, size, 0, 0 );
  if( val )
    return( *val );
  return( NULL );
}

/* ------------------------------
   Function: hash_tree_lookup_hash
     Lookup the hash tree entry associated with a specified key and hash value.

   Parameters:
     trunk - a trunk of a hash tree to operate on
     key   - a key to use to identify an entry
     size  - the size of the key
     hash  - an initial hash value for this key

   Returns:
     If an entry associated with a specified key exists its value is returned,
     NULL otherwise.
   ------------------------------ */
void *hash_tree_lookup_hash( HashTreeNode *trunk, void *key, size_t size,
			     unsigned int hash ) {
  void **val = hash_tree_access( trunk, key, size, hash, 0 );
  if( val )
    return( *val );
  return( NULL );
}

/* ------------------------------
   Function: hash_tree_set
     Create or replace the a tree entry associated with a specified key.

   Parameters:
     trunk - a trunk of a hash tree to operate on
     key   - a key to use to identify an entry
     size  - the size of the key
     value - value to use for this entry

   Returns:
     Non-zero on success, zero on failure.
   ------------------------------ */
int hash_tree_set( HashTreeNode *trunk, void *key, size_t size, void *value ) {
  void **val = hash_tree_access( trunk, key, size, 0, 1 );
  if( val ) {
    *val = value;
    return( 1 );
  }
  return( 0 );
}

/* ------------------------------
   Function: hash_tree_foreach
     Call a function on every entry in the table.

   Parameters:
     trunk - a trunk of a hash tree to operate on
     func  - a function to call for each entry in the table
     ud    - user specified data to pass when calling the function
   ------------------------------ */
void hash_tree_foreach( HashTreeNode *trunk,
			void (*func)( void *, size_t, void *, void * ),
			void *ud ) {
  HashTreeNode *branch;
  size_t i;

  for( i = 0; i < trunk->size; i++ ) {
    branch = trunk->o.nodes[i];
    while( branch ) {
      func( branch->key, branch->size, branch->o.data, ud );
      branch = branch->next;
    }
  }
}

void **hash_tree_lookup0( HashTreeNode *trunk, void *key, size_t size ) {
  int i = 1;
  HashTreeNode **branch, *bptr;
  char *str = (char *)key;

  if( !trunk )
    return( NULL );

  branch = &(trunk->o.nodes[(int)str[0]]);
  while( *branch && !(*branch)->key && str[i] ) {
    branch = &((*branch)->o.nodes[(int)str[i]]);
    ++i;
  }

  if( !*branch ) {
    *branch = hash_tree_node_new( str, strlen(str) );
    trunk->size++;
  } else if( (*branch)->key ) {
    if( strcmp( (*branch)->key, str ) ) {
      bptr = *branch;

      *branch = hash_tree_node_new( NULL, trunk->size );
      while( ((char *)bptr->key)[i] == str[i] ) {
	branch = &((*branch)->o.nodes[(int)str[i]]);
	*branch = hash_tree_node_new( NULL, trunk->size );
	++i;
      }

      (*branch)->o.nodes[(int)((char *)bptr->key)[i]] = bptr;
      branch = &((*branch)->o.nodes[(int)str[i]]);
      *branch = hash_tree_node_new( str, strlen(str) );
      trunk->size++;
    }
  } else { // this means str[i] == 0
    branch = &((*branch)->o.nodes[0]);
    if( !*branch ) {
      *branch = hash_tree_node_new( str, strlen(str) );
      trunk->size++;
    }
  }

  return( &((*branch)->o.data) );
}

void **hash_tree_lookup2( HashTreeNode *trunk, void *key, size_t size ) {
  HashTreeNode *branch, *bp;
  unsigned int hash = 0, hwrap, hash2, tippt;
  int i, cmp;

  while( 1 ) {
    hash = fnv_1a_hash( key, size, hash );
    hwrap = hash%trunk->size;
    tippt = MAX( trunk->size >> 2, 11 );

    if( trunk->o.nodes[hwrap] && trunk->o.nodes[hwrap]->key ) {
      cmp = strcmp( trunk->o.nodes[hwrap]->key, key );
      if( cmp == 0 )
	return( &(trunk->o.nodes[hwrap]->o.data) );
      if( cmp > 0 ) {
	i = 0;
	bp = trunk->o.nodes[hwrap];
	while( bp->next && (cmp = strcmp( bp->next->key, key )) ) {
	  bp = bp->next;
	  i++;
	}
	if( i < tippt ) {
	  branch = hash_tree_node_new( key, size );
	  branch->next = bp->next;
	  bp->next = branch;
	  return( &(branch->o.data) );
	} else {
	  branch = hash_tree_node_new( NULL, trunk->size >> 1 );
	  while( trunk->o.nodes[hwrap] ) {
	    hash2 = fnv_1a_hash( key, size, hash );
	    bp = trunk->o.nodes[hwrap]->next;
	  }
	}
      } else {
	i = 0;
	bp = trunk->o.nodes[hwrap];
	while( bp ) {
	  bp = bp->next;
	  i++;
	}
	if( i < tippt ) {
	  branch = hash_tree_node_new( key, size );
	  branch->next = trunk->o.nodes[hwrap];
	  trunk->o.nodes[hwrap] = branch;
	  return( &(branch->o.data) );
	} else {
	  bp = hash_tree_node_new( NULL, trunk->size >> 1 );
	}
      }
    }
  }
  
}


/**********************************************\

   Section: Universal Linked List Routines

\**********************************************/

/* ------------------------------
   Function: direct_list_append
   ------------------------------ */
void *direct_list_append( void *list, int next, int prev, void *node ) {
  void *nextp, *ptr = list;

  if( !ptr ) {
    if( prev >= 0 )
      C_STRUCT_MEMBER( void *, node, prev ) = NULL;
    C_STRUCT_MEMBER( void *, node, next ) = NULL;
    return( node );
  }

  while( ptr && (nextp = C_STRUCT_MEMBER( void *, ptr, next )) )
    ptr = nextp;

  if( prev >= 0 ) {
    C_STRUCT_MEMBER( void *, node, prev ) = ptr;
    if( C_STRUCT_MEMBER( void *, ptr, next ) )
      C_STRUCT_MEMBER( void *, C_STRUCT_MEMBER( void *, ptr, next ), prev ) = node;
  }
  C_STRUCT_MEMBER( void *, node, next ) = C_STRUCT_MEMBER( void *, ptr, next );
  C_STRUCT_MEMBER( void *, ptr, next ) = node;

  return( list );
}

/* ------------------------------
   Function: direct_list_prepend
   ------------------------------ */
void *direct_list_prepend( void *list, int next, int prev, void *node ) {
  if( prev >= 0 )
    C_STRUCT_MEMBER( void *, node, prev ) = NULL;
  C_STRUCT_MEMBER( void *, node, next ) = list;

  return( node );
}

/* ------------------------------
   Function: direct_list_insert
   ------------------------------ */
void *direct_list_insert( void *list, int next, int prev, void *node,
			  size_t at ) {
  size_t i = 0;
  void *nextp, *ptr = list;

  if( !ptr ) {
    if( prev >= 0 )
      C_STRUCT_MEMBER( void *, node, prev ) = NULL;
    C_STRUCT_MEMBER( void *, node, next ) = NULL;
    return( node );
  }

  while( ptr && (nextp = C_STRUCT_MEMBER( void *, ptr, next )) && i++ < at )
    ptr = nextp;

  if( prev >= 0 ) {
    C_STRUCT_MEMBER( void *, node, prev ) = ptr;
    if( C_STRUCT_MEMBER( void *, ptr, next ) )
      C_STRUCT_MEMBER( void *, C_STRUCT_MEMBER( void *, ptr, next ), prev ) = node;
  }
  C_STRUCT_MEMBER( void *, node, next ) = C_STRUCT_MEMBER( void *, ptr, next );
  C_STRUCT_MEMBER( void *, ptr, next ) = node;

  return( list );
}

/* ------------------------------
   Function: direct_list_insert_sorted
   ------------------------------ */
void *direct_list_insert_sorted( void *list, int next, int prev, void *node,
				 DListCompareFunc compare ) {
  void *nextp, *ptr = list;

  if( !ptr || compare( list, node ) < 0 ) {
    if( prev >= 0 )
      C_STRUCT_MEMBER( void *, node, prev ) = NULL;
    C_STRUCT_MEMBER( void *, node, next ) = NULL;
    return( node );
  }

  while( ptr && (nextp = C_STRUCT_MEMBER( void *, ptr, next )) &&
	 compare( nextp, node ) < 0 )
    ptr = nextp;

  if( prev >= 0 ) {
    C_STRUCT_MEMBER( void *, node, prev ) = ptr;
    if( C_STRUCT_MEMBER( void *, ptr, next ) )
      C_STRUCT_MEMBER( void *, C_STRUCT_MEMBER( void *, ptr, next ), prev ) = node;
  }
  C_STRUCT_MEMBER( void *, node, next ) = C_STRUCT_MEMBER( void *, ptr, next );
  C_STRUCT_MEMBER( void *, ptr, next ) = node;

  return( list );
}

/* ------------------------------
   Function: direct_list_remove
   ------------------------------ */
void *direct_list_remove( void *list, int next, int prev, void *node ) {
  if( list == node )
    return( C_STRUCT_MEMBER( void *, node, next ) );

  if( prev >= 0 ) {
    if( C_STRUCT_MEMBER( void *, node, next ) )
      C_STRUCT_MEMBER( void *, C_STRUCT_MEMBER( void *, node, next ), prev ) = 
	C_STRUCT_MEMBER( void *, node, prev );

    if( C_STRUCT_MEMBER( void *, node, prev ) )
      C_STRUCT_MEMBER( void *, C_STRUCT_MEMBER( void *, node, prev ), next ) = 
	C_STRUCT_MEMBER( void *, node, next );
  } else {
    void *nextp, *ptr = list;

    while( ptr && (nextp = C_STRUCT_MEMBER( void *, ptr, next )) &&
	   nextp != node )
      ptr = nextp;

    C_STRUCT_MEMBER( void *, ptr, next ) = C_STRUCT_MEMBER( void *, node, next );
  }

  return( list );
}

/* ------------------------------
   Function: direct_list_remove_each
   ------------------------------ */
void *direct_list_remove_each( void *list, int next, int prev,
			       int (*compare)( void *a, void *b ),
			       int (*callback)( void *node, void *user_data ),
			       void *user_data ) {
  return( list );
}

/* ------------------------------
   Function: direct_list_foreach
   ------------------------------ */
void direct_list_foreach( void *list, int next, int prev, void *node,
			  int (*callback)( void *node, void *user_data ),
			  void *user_data ) {
  while( list && callback( list, user_data ) )
    list = C_STRUCT_MEMBER( void *, list, next );
}

/* ------------------------------
   Function: direct_list_search
   ------------------------------ */
void *direct_list_search( void *list, int next, int data, void *val ) {
  while( list && C_STRUCT_MEMBER( void *, list, data ) != val )
    list = C_STRUCT_MEMBER( void *, list, next );

  return( list );
}

/* ------------------------------
   Function: direct_list_length
   ------------------------------ */
size_t direct_list_length( void *list, int next ) {
  size_t len = 0;

  while( list ) {
    ++len;
    list = C_STRUCT_MEMBER( void *, list, next );
  }

  return( len );
}

/* ------------------------------
   Function: direct_list_nth
   ------------------------------ */
void *direct_list_nth( void *list, int next, size_t n ) {
  while( list && n-- )
    list = C_STRUCT_MEMBER( void *, list, next );

  return( list );
}


/* ------------------------------
   Function: data_list_append
   ------------------------------ */
DataList *data_list_append( DataList *list, void *data ) {
  DataList *node = c_new0( DataList, 1 );
  node->data = data;
  return( (DataList *)direct_list_append( list,
					  C_STRUCT_OFFSET( DataList, next ), 0,
					  node ) );
}

/* ------------------------------
   Function: data_list_prepend
   ------------------------------ */
DataList *data_list_prepend( DataList *list, void *data ) {
  DataList *node = c_new0( DataList, 1 );
  node->data = data;
  return( (DataList *)direct_list_prepend( list,
					   C_STRUCT_OFFSET( DataList, next ), 0,
					   node ) );
}

/* ------------------------------
   Function: data_list_insert
   ------------------------------ */
DataList *data_list_insert( DataList *list, void *data, size_t at ) {
  DataList *node = c_new0( DataList, 1 );
  node->data = data;
  return( (DataList *)direct_list_insert( list,
					  C_STRUCT_OFFSET( DataList, next ), 0,
					  node, at ) );
}

/* ------------------------------
   Function: data_list_insert_sorted
   ------------------------------ */
DataList *data_list_insert_sorted( DataList *list, void *data,
				   DListCompareFunc compare ) {
  DataList *node = c_new0( DataList, 1 );
  node->data = data;
  return( (DataList *)
	  direct_list_insert_sorted( list, C_STRUCT_OFFSET( DataList, next ), 0,
				     node, compare ) );
}

/* ------------------------------
   Function: data_list_remove
   ------------------------------ */
DataList *data_list_remove( DataList *list, void *data ) {
  DataList *node, *node2;

  if( list->data == data ) {
    node = list->next;
    free( list );
    return( node );
  }

  node = list;
  while( node->next ) {
    if( node->next->data == data ) {
      node2 = node->next->next;
      free( node->next );
      node->next = node2;
    } else {
      node = node->next;
    }
  }
  return( list );
}

/* ------------------------------
   Function: data_list_remove_each
   ------------------------------ */
DataList *data_list_remove_each( DataList *list, DListCompareFunc compare,
				 int (*callback)( void *node, void *user_data ),
				 void *user_data ) {
  return( list );
}

/* ------------------------------
   Function: pt2edge3f
     Find the minimum distance from a 3D point to a 3D edge. The edge is
     defined as the line segment spanning between two verticies.

   Parameters:
     pt   - a point specified as a 3 element array
     vrt1 - first vertex of a line segment specified as a 3 element array
     vrt2 - second vertex of a line segment specified as a 3 element array

   Returns:
     distance from point to edge
   ------------------------------ */
float pt2edge3f( float *pt, float *vrt1, float *vrt2 ) {
  // store the edge vector
  float dv[3] = minus_init3v( vrt2, vrt1 ), dp[3] = minus_init3v( pt, vrt1 );
  // calc the magnitude of the edge vector
  float mag = sqrt( dot3v( dv, dv ) );
  // calc the normalized projection of the point onto the edge
  float prj = dot3v( dp, dv )/(mag*mag);

  if( prj < 0.0 ) { // if projected point is before the first vertex
    minus3v( dv, pt, vrt1 );
  } else if( prj > 1.0 ) { // if projected point is after second vertex
    minus3v( dv, pt, vrt2 );
  } else {
    dv[0] = pt[0] - (vrt1[0] + dv[0]*prj);
    dv[1] = pt[1] - (vrt1[1] + dv[1]*prj);
    dv[2] = pt[2] - (vrt1[2] + dv[2]*prj);
  }

  // dist of point and projection
  return( hypot3v( dv ) );
}

/* ------------------------------
   Function: pt2poly3f
     Find the minimum distance from a 3D point to a convex 3D polygon. The
     polygon is specified as the area enclosed by a given array of verticies.

   Parameters:
     pt      - a point specified as a 3 element array
     numvrts - the number of verticies used to define this polygon
     stride  - the number of elements (floats) from the beginning of one vertex
               to the beginning of the next vertex in the vertex array
     vrts    - an array of verticies which enclose a polygon

   Returns:
     distance from point to polygon
   ------------------------------ */
float pt2poly3f( float *pt, int numvrts, int stride, float *vrts ) {
  int i0, i1, numords = numvrts*stride;
  float dist, min = FLT_MAX;

  i0 = 0;
  for( i1 = stride; i1 < numords; i1 += stride ) {
    dist = pt2edge3f( pt, &vrts[i0], &vrts[i1] );
    if( dist < min )
      min = dist;
    i0 = i1;
  }
  return( min );
}

/* ------------------------------
   Function: poly2poly3f
     Find the minimum distance from one convex 3D polygon to another. Polygons
     are specified as the area enclosed by a given array of verticies.

   Parameters:
     pt       - a point specified as a 3 element array
     numvrts1 - the number of verticies used to define the first polygon
     stride   - the number of elements (floats) from the beginning of one vertex
                to the beginning of the next vertex in the vertex array
     vrts1    - an array of verticies which enclose the first polygon
     numvrts2 - the number of verticies used to define the second polygon
     vrts2    - an array of verticies which enclose the second polygon

   Returns:
     distance from polygon1 to polygon2
   ------------------------------ */
float poly2poly3f( int numvrts1, int stride, float *vrts1, int numvrts2, float *vrts2 ) {
  int i, numords1 = numvrts1*stride, numords2 = numvrts2*stride;
  float dist, min = FLT_MAX;

  for( i = 0; i < numords1; i += stride ) {
    dist = pt2poly3f( &vrts1[i], numvrts2, stride, vrts2 );
    if( dist < min )
      min = dist;
  }
  for( i = 0; i < numords2; i += stride ) {
    dist = pt2poly3f( &vrts2[i], numvrts1, stride, vrts1 );
    if( dist < min )
      min = dist;
  }
  return( min );
}

/* ------------------------------
   Function: edgeintersect3f
   ------------------------------ */
int edgeintersect3f( float *vrt1, float *vrt2, float *vrt3, float *vrt4,
		     float *pta, float *ptb, double *prja, double *prjb ) {
  float dv13[3] = minus_init3v( vrt1, vrt3 );
  float dv43[3] = minus_init3v( vrt4, vrt3 );
  float dv21[3] = minus_init3v( vrt2, vrt1 );
  double d1343 = dot3v( dv13, dv43 );
  double d4321 = dot3v( dv43, dv21 );
  double d1321 = dot3v( dv13, dv21 );
  double d4343 = dot3v( dv43, dv43 );
  double d2121 = dot3v( dv21, dv21 );
  double denom = d2121*d4343 - d4321*d4321;

  // sanity
  if( (ABS(dv43[0]) < FLT_EPSILON && ABS(dv43[1]) < FLT_EPSILON &&
       ABS(dv43[2]) < FLT_EPSILON) ||
      (ABS(dv21[0]) < FLT_EPSILON && ABS(dv21[1]) < FLT_EPSILON &&
       ABS(dv21[2]) < FLT_EPSILON) ) // lines have no length
    return( 0 );
  if( ABS(denom) < DBL_EPSILON ) // lines parallel
    return( 0 );

  // project and populate onto line 'a'
  *prja = (d1343*d4321 - d1321*d4343)/denom;
  pta[0] = vrt1[0] + *prja*dv21[0];
  pta[1] = vrt1[1] + *prja*dv21[1];
  pta[2] = vrt1[2] + *prja*dv21[2];
  // project and populate onto line 'b'
  *prjb = (d1343 + d4321*(*prja))/d4343;
  ptb[0] = vrt3[0] + *prjb*dv43[0];
  ptb[1] = vrt3[1] + *prjb*dv43[1];
  ptb[2] = vrt3[2] + *prjb*dv43[2];

  return( 1 );
}

/* ------------------------------
   Function: edgeintersect2f
   ------------------------------ */
int edgeintersect2f( float *vrt1, float *vrt2, float *vrt3, float *vrt4,
		     float *pti, double *prja, double *prjb ) {
  float dv13[3] = minus_init2v( vrt1, vrt3 );
  float dv43[3] = minus_init2v( vrt4, vrt3 );
  float dv21[3] = minus_init2v( vrt2, vrt1 );
  double denom = dv43[1]*dv21[0] - dv43[0]*dv21[1];

  // sanity
  if( (ABS(dv43[0]) < FLT_EPSILON && ABS(dv43[1]) < FLT_EPSILON) ||
      (ABS(dv21[0]) < FLT_EPSILON && ABS(dv21[1]) < FLT_EPSILON) )
    return( 0 );

  denom = dv43[1]*dv21[0] - dv43[0]*dv21[1];
  if( ABS(denom) < DBL_EPSILON )
    return( 0 );
  *prja = (dv13[1]*dv43[0] - dv13[0]*dv43[1])/denom;
  *prjb = (dv13[1]*dv21[0] - dv13[0]*dv21[1])/denom;
  pti[0] = vrt1[0] + *prja*dv21[0];
  pti[1] = vrt1[1] + *prja*dv21[1];

  return( 1 );
}


int c_prime_ceiling( int x ) {
  static int primes[] = {
    3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 
    53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 
    113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 
    193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 
    271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 
    359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 
    443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 
    541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 
    619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 
    719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 
    821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 
    911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 
    1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 
    1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 
    1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 
    1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 
    1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 
    1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 
    1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 
    1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 
    1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 
    1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 
    2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 
    2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 
    2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 
    2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 
    2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 
    2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 
    2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 
    2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 
    2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 
    3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 
    3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 
    3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 
    3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 
    3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 
    3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 
    3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 
    3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 
    3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 
    4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 
    4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 
    4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 
    4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 
    4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 
    4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 
    4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 
    4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 
    5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 
    5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 
    5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 
    5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 
    5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 
    5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 
    5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 
    5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 
    6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 
    6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 
    6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 
    6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 
    6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 
    6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 
    6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 
    6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 
    6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 
    7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 
    7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 
    7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 
    7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 
    7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 
    7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 
    7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 
    8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, 8123, 8147, 
    8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, 8263, 8269, 
    8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 
    8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, 
    8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 
    8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 
    8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 
    8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 
    9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 
    9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 
    9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 
    9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 
    9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 9643, 
    9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 
    9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 
    9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 0 };
  int i;

  for( i = 0; primes[i]; i++ )
    if( primes[i] > x )
      return( primes[i] );
  return( x );
}

/**********************************************\

   Section: Filesystem

\**********************************************/

#ifdef __USE_POSIX
#include <wordexp.h> // POSIX standard word expand
#endif
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>

/* ------------------------------
   Function: dir_entries_get
     Get a list of directory entries that matches a _pattern_.

   Parameters:
     pattern - a file or directory pattern as would be interpreted by a shell
     count   - [out] the number of entries found

   Returns:
     a <DirEntry> list of matches
   ------------------------------ */
DirEntry *dir_entries_get( char *pattern, int *count ) {
 DirEntry *flist = NULL, *tail = NULL, *tmppt;
 char *filename, *dirsep;
 struct stat filestat;
 struct tm *time_data;
#ifdef __USE_POSIX
 wordexp_t explist;
 int i;
#else
 WIN32_FIND_DATA FindFileData;
 HANDLE hFind;
#endif


#ifdef __USE_POSIX
 wordexp( pattern, &explist, 0 );
 printf( "dir entry pattern '%s' matches %d\n", pattern, explist.we_wordc );
 for( i = 0; i < explist.we_wordc; i++ )
#else
 hFind = FindFirstFile( pattern, &FindFileData );
 do
#endif
  {
   // set name
#ifdef __USE_POSIX
   filename = explist.we_wordv[i];
#else
   filename = FindFileData.cFileName;
#endif

   if( strcmp( filename, "." ) != 0 && strcmp( filename, ".." ) != 0 ) {
     // add a new dir entry node for this file
     tmppt = c_new0( DirEntry, 1 );
     if( !flist )
       flist = tmppt;
     else
       tail->next = tmppt;
     tail = tmppt;

     // add file status info
     stat( filename, &filestat );
     dirsep = strrchr( filename, DIR_ENTRY_DIRSEP_CHR );
     if( dirsep )
       tmppt->name = strdup( dirsep + 1 );
     else
       tmppt->name = strdup( filename );
     if( S_ISDIR(filestat.st_mode) )
       tmppt->type = DIR_ENTRY_DIRECTORY;
     else if( S_ISCHR(filestat.st_mode) )
       tmppt->type = DIR_ENTRY_CHAR_DEV;
     else if( S_ISBLK(filestat.st_mode) )
       tmppt->type = DIR_ENTRY_BLOCK_DEV;
     else if( S_ISFIFO(filestat.st_mode) )
       tmppt->type = DIR_ENTRY_FIFO;
#ifdef __USE_POSIX
     else if( S_ISSOCK(filestat.st_mode) )
       tmppt->type = DIR_ENTRY_SOCKET;
#endif
     else
       tmppt->type = DIR_ENTRY_REG_FILE;
     tmppt->size = filestat.st_size;
     time_data = localtime( &filestat.st_mtime );
     tmppt->day = time_data->tm_mday;
     tmppt->month = time_data->tm_mon;
     tmppt->year = time_data->tm_year;
     tmppt->hour = time_data->tm_hour;
     tmppt->minute = time_data->tm_min;
     tmppt->second = time_data->tm_sec;

     *count++;
   }
  }
#ifdef __USE_POSIX
  wordfree( &explist );
#else
  while( FindNextFile( hFind, &FindFileData ) );
  FindClose( hFind );
#endif

 return( flist );
}
