/***             analog 4.14             http://www.analog.cx/             ***/
/*** This program is copyright (c) Stephen R. E. Turner 1995 - 2001 except as
 *** stated otherwise. Distribution, usage and modification of this program is
 *** subject to the conditions of the Licence which you should have received
 *** with it. This program comes with no warranty, expressed or implied.   ***/

#include "anlghea3.h"

/*** tree.c; functions for tree construction and processing. ***/

/* first, treefind(), analogous to hashfind() */
/* NB Don't really need as much data as Hashindex *, but allows us to use
   previous sort routines etc. */
Hashindex *treefind(char *name, char *nameend, Hashtable **tree,
		    Hashindex *item, cutfnp cutfn, logical build,
		    logical transient, logical reuse, Memman *space) {
  Hashindex *lp, *lastlp, *newone;   /* not called new because of C++ */
  unsigned long magic;

  if (*tree == NULL && !build)
    return(NULL);
  else if (*tree == NULL)
    *tree = rehash(NULL, TREEHASHSIZE, space);
  else if (TOO_FULL_TREE((*tree)->n, (*tree)->size))
    *tree = rehash(*tree, NEW_SIZE_TREE((*tree)->size), space);
  if (build && (strchr(name, '*') != NULL || strchr(name, '?') != NULL))
    magic = 0;
  else
    MAGICNOTREE(magic, name, nameend, (*tree)->size);

  lp = (*tree)->head[magic];
  lastlp = NULL;

  while (TRUE) {
    if (lp == NULL) { /* not found */
      if (build) {
	newone = newtreeentry(name, nameend, item, transient, reuse, space);
	if (lastlp == NULL)
	  (*tree)->head[magic] = newone;
	else
	  lastlp->next = newone;
	((*tree)->n)++;
	if (cutfn != NULL) {
	  cutfn(&name, &nameend, item->name, build);
	  if (name != NULL)
	    (void)treefind(name, nameend, (Hashtable **)&(newone->other),
			   item, cutfn, build, transient, reuse, space);
	}
      }
      else {  /* !build */
	for (newone = NULL, lp = (*tree)->head[0]; lp != NULL; TO_NEXT(lp)) {
	  if (genwildmatch(name, nameend, lp->name)) {
	    if (newone == NULL) {
	      newone = newtreeentry(name, nameend, item, transient, reuse,
				    space);
	      if (lastlp == NULL)
		(*tree)->head[magic] = newone;
	      else
		lastlp->next = newone;
	      ((*tree)->n)++;
	    }
	    graft((Hashtable **)&(newone->other), (Hashtable *)(lp->other),
		  space);
	  }
	}
	if (newone != NULL) {
	  if (cutfn != NULL) {
	    cutfn(&name, &nameend, item->name, build);
	    if (name != NULL)
	      (void)treefind(name, nameend, (Hashtable **)&(newone->other),
			     item, cutfn, build, transient, reuse, space);
	  }
	}
      }
      return(newone);
    }
    else if (genstreq(name, nameend, lp->name)) {  /* found it */
      if (lp->own->reused)
	lp->own = newtreedata(lp->own, space);
      treescore(lp->own, item->own);
      if (cutfn != NULL) {
	cutfn(&name, &nameend, item->name, build);
	if (name != NULL)
	  (void)treefind(name, nameend, (Hashtable **)&(lp->other),
			 item, cutfn, build, transient, reuse, space);
      }
      return(lp);
    }
    else {
      lastlp = lp;
      TO_NEXT(lp);
    }
  }
}

void graft(Hashtable **newone, Hashtable *old, Memman *space) {
  Hashindex *lp, *found;
  unsigned long magic;
  if (old != NULL) {
    for (magic = 0; magic < old->size; magic++) {
      for (lp = old->head[magic]; lp != NULL; TO_NEXT(lp)) {
	found = treefind(lp->name, strchr(lp->name, '\0'), newone, lp,
			 NULL, TRUE, FALSE, FALSE, space);
	graft((Hashtable **)&(found->other), (Hashtable *)(lp->other),
	      space);
      }
    }
  }
}

void allgraft(Hashtable *t, Memman *space) {
  Hashindex *lp, *lp0;
  unsigned long magic;

  if (t != NULL) {
    for (magic = 1; magic < t->size; magic++) {
      for (lp = t->head[magic]; lp != NULL; TO_NEXT(lp)) {
	for (lp0 = t->head[0]; lp0 != NULL; TO_NEXT(lp0)) {
	  if (MATCHES(lp->name, lp0->name)) {
	    graft((Hashtable **)&(lp->other), (Hashtable *)(lp0->other),
		  space);
	  }
	}
	allgraft((Hashtable *)(lp->other), space);
      }
    }
  }
}

Hashindex *newtreeentry(char *name, char *nameend, Hashindex *item,
			logical transient, logical reuse, Memman *space) {
  Hashindex *ans = (Hashindex *)submalloc(space, sizeof(Hashindex));

  if (*nameend == '\0' && !transient)
    ans->name = name;
  else {
    ans->name = (char *)submalloc(space, (size_t)(nameend - name + 1));
    memcpy((void *)(ans->name), (void *)name, (size_t)(nameend - name));
    ans->name[(size_t)(nameend - name)] = '\0';
  }
  if (reuse)
    ans->own = item->own;  /* will get newtreedata if hit a 2nd time */
  else
    ans->own = newtreedata(item->own, space);
  ans->other = NULL;
  ans->next = NULL;
  return(ans);
}

Hashentry *newtreedata(Hashentry *from, Memman *space) {
  Hashentry *ans = (Hashentry *)submalloc(space, sizeof(Hashentry));
  int i;

  for (i = 0; i < DATA_NUMBER; i++)
    ans->data[i] = 0;
  ans->bytes = 0.0;
  ans->ispage = from->ispage;  /* this is good enough */
  ans->reused = FALSE;
  /* Basically, when we first create a node on the tree, we just store as its
     data a pointer to the data of the item which caused it to be created. But
     we can't accumulate more data there (that would change the original item),
     so if we hit it again, we call this function to create new space. Setting
     the "reused" flag is a signal that we can safely accumulate in it on
     future hits. */
  treescore(ans, from);
  return(ans);
}

void treescore(Hashentry *to, Hashentry *from) {
  int i;

  for (i = 0; i < COUNT_NUMBER; i++)
    to->data[i] += from->data[i];
  for ( ; i < DATA_NUMBER; i++)
    to->data[i] = MAX(to->data[i], from->data[i]);
  to->bytes += from->bytes;
}

Hashindex *sorttree(Hashtable *tree, choice rep, Floor *floor, choice sortby,
		    Floor *subfloor, choice subsortby, logical alphaback,
		    unsigned int level, Strlist *partname, Include *wanthead,
		    Alias *notcorrupt, choice requests,
		    choice date, unsigned long totr, unsigned long totp,
		    double totb, unsigned long maxr, unsigned long maxp,
		    double maxb, Hashentry **badp, unsigned long *badn,
		    Memman *space) {
  Hashindex *gooditems, *baditems, *p, *np;
  Strlist *pn, sp;
  size_t need = (size_t)level + 3;
  Alias *ap;
  unsigned long i;
  logical ok;

  unhash(tree, &gooditems, &baditems);
  if (notcorrupt != NULL) {    /* NB notcorrupt only used for doms */
    baditems = (Hashindex *)submalloc(space, sizeof(Hashindex));
    baditems->name = "\v";
    baditems->own = (Hashentry *)submalloc(space, sizeof(Hashentry));
    for (i = 0; i < DATA_NUMBER; i++)
      baditems->own->data[i] = 0;
    baditems->own->bytes = 0.0;
    baditems->own->ispage = FALSE;
    baditems->other = NULL;
    baditems->next = NULL;
    for (p = gooditems, np = NULL; p != NULL; TO_NEXT(p)) {
      for (ok = FALSE, ap = notcorrupt; !ok && ap != NULL; TO_NEXT(ap)) {
	if (STREQ(p->name, ap->from))
	  ok = TRUE;
      }
      if (ok)
	np = p;
      else {   /* erase p from list */
	if (strchr(p->name, '*') == NULL && strchr(p->name, '?') == NULL)
	  debug('U', "%s", p->name);
	treescore(baditems->own, p->own);
	if (np == NULL)
	  gooditems = p->next;
	else
	  np->next = p->next;
      }
    }
  }   /* end notcorrupt != NULL */
  for (i = 0; i < tree->size; i++)
    tree->head[i] = NULL;
  for (pn = partname; pn != NULL; TO_NEXT(pn))
    need += strlen(pn->name);
  my_sort(&gooditems, &baditems, partname, &pn, &sp, need, rep, floor,
	  sortby, alphaback, wanthead, requests, date, totr, totp, totb, maxr,
	  maxp, maxb);
  if (badp != NULL) {
    *badp = newhashentry(FALSE);
    *badn = 0;
    for (p = baditems; p != NULL; TO_NEXT(p)) {
      if (p->own != NULL && p->own->data[requests] > 0) {
	(*badp)->data[requests] += p->own->data[requests];
	(*badp)->data[PAGES] += p->own->data[PAGES];
	(*badp)->bytes += p->own->bytes;
	(*badp)->data[date] = MAX((*badp)->data[date],
				    p->own->data[date]);
	(*badn)++;
      }
    }
  }
  for (p = gooditems; p != NULL; TO_NEXT(p)) {
    if (p->other != NULL) {
      (void)maketreename(partname, p, &pn, &sp, need, rep, FALSE);
      ((Hashtable *)(p->other))->head[0] =
	sorttree((Hashtable *)(p->other), rep, subfloor, subsortby, subfloor,
		 subsortby, alphaback, level + 1, pn, wanthead, NULL,
		 requests, date, totr, totp, totb, maxr, maxp, maxb, NULL,
		 NULL, space);
    }
  }
  return(gooditems);
}

void maketree(Tree *treex, Hashindex *gooditems, Hashindex *baditems) {
  Hashindex *p;
  char *name, *nameend;

  for (p = gooditems; p != NULL; TO_NEXT(p)) {
    if (p->own != NULL) {
      name = NULL;
      treex->cutfn(&name, &nameend, p->name, FALSE);
      (void)treefind(name, nameend, &(treex->tree), p, treex->cutfn,
		     FALSE, FALSE, TRUE, treex->space);
    }
  }
  for (p = baditems; p != NULL; TO_NEXT(p)) {
    if (p->own != NULL) {
      name = NULL;
      treex->cutfn(&name, &nameend, p->name, FALSE);
      (void)treefind(name, nameend, &(treex->tree), p, treex->cutfn,
		     FALSE, FALSE, TRUE, treex->space);
    }
  }
}

void makederived(Derv *derv, Hashindex *gooditems, Hashindex *baditems,
		 unsigned char convfloor, logical multibyte, choice rep) {
  extern Hashentry *blank_entry;

  Hashindex *p;
  Hashentry *f;
  char *name, *nameend;
  size_t len;
  logical donegood;

  for (p = gooditems, donegood = FALSE; p != NULL || !donegood; ) {
    if (p == NULL) {
      donegood = TRUE;
      p = baditems;
    }
    else {
      if (p->own != NULL) {
	name = NULL;
	for (derv->cutfn(&name, &nameend, p->name, *(derv->list));
	     name != NULL;
	     derv->cutfn(&name, &nameend, p->name, *(derv->list))) {
	  len = nameend - name;
	  memcpy(submalloc(derv->space, len + 1), (void *)name, len);
	  *((char *)(derv->space->next_pos) - 1) = '\0';
	                       /* = curr_pos + len */
	  f = (Hashentry *)(hashfind(derv->space, &(derv->table), NULL,
				     UNSET, NULL, NULL, "", 0, FALSE,
				     convfloor, multibyte, rep, FALSE)->other);
	  if (!ENTRY_BLANK(f))
	    treescore(f, p->own);
	}
      }
      TO_NEXT(p);
    }
  }
}

char *maketreename(Strlist *pn, Hashindex *p, Strlist **newpn, Strlist *space,
		   size_t need, choice rep, logical delims) {
  /* Compile name from strlist. We end up doing the most-significant portion
     several times, but it really is the best way, at least if the name is
     little-endian, otherwise we have to try and work out which portion we
     want when we go back down a level. */
  static char *name = NULL;
  static size_t len = 0;
  logical back;
  char glue;
  char *t;
  Strlist *sp;
  size_t l;

  if (rep == REP_OS)    /* just use last component of name */
    return(p->name);

  if (rep == REP_TYPE || rep == REP_DOM || rep == REP_ORG) {
    back = !(rep == REP_DOM && pn != NULL && STREQ(pn->name, "\f"));
    /* back = TRUE unless numerical subdomains */
    glue = '.';
  }
  else {
    back = FALSE;
    if (rep == REP_DIR || rep == REP_REFSITE)
      glue = '/';
    else if (rep == REP_BROWSUM)
      glue = '.';  /* see below */
    else
      glue = '?';
  }

  /* make newpn */
  space->name = p->name;
  space->next = NULL;
  ENSURE_LEN(name, len, need + strlen(p->name));
  if (back || pn == NULL) {
    space->next = pn;
    *newpn = space;
  }
  else {
    for (sp = pn; sp->next != NULL; TO_NEXT(sp))
      ;
    sp->next = space;
    space->next = NULL;
    *newpn = pn;
  }

  /* assemble newpn */
  sp = *newpn;
  if (rep == REP_DOM && !back) /* i.e. numerical subdomain: special case */
    TO_NEXT(sp);
  t = name;
  if (rep == REP_TYPE && delims)  /* delimiter at start */
    *(t++) = glue;
  if (rep == REP_BROWSUM && sp != NULL) {
    /* special case to enable two different delimiters */
    l = strlen(sp->name);
    memcpy(t, sp->name, l);
    t += l;
    if (sp->next != NULL)
      *(t++) = '/';
    TO_NEXT(sp);
  }
  for ( ; sp != NULL; TO_NEXT(sp)) {
    l = strlen(sp->name);
    memcpy(t, sp->name, l);
    t += l;
    if (sp->next != NULL)
      *(t++) = glue;
  }
  if (delims && (rep == REP_REFSITE || rep == REP_DIR))  /* delimiter at end */
    *(t++) = glue;
  *t = '\0';

  return(name);
}

logical sublevels(Hashtable *tree) {
  /* check if a tree has anything below the top level */
  Hashindex *p;
  unsigned long i;

  if (tree != NULL) {
    for (i = 0; i < tree->size; i++) {
      for (p = tree->head[i]; p != NULL; TO_NEXT(p)) {
	if (p->other != NULL)
	  return(TRUE);
      }
    }
  }
  return(FALSE);
}

/* genstreq is like streq but takes double-pointer strings */
logical genstreq(char *a, char *b, char *t) {
  for ( ; *a == *t && a < b; a++)
    t++;
  if (a == b && *t == '\0')
    return(TRUE);
  else
    return(FALSE);
}

/* Now the various nextname functions, which vary by type of item. There are
   two patterns, cutfnp and dcutfnp. The former is used in building a tree
   report, the latter in building a dervrep or dervtreerep.

   Pattern for cutfnp (up to pnextname) is
   void ?nextname(char **name, char **nameend, char *whole, logical build).
   whole is whole name; [*name, *nameend) is last chunk (*name == NULL if
   none); build is because behaviour of leading/trailing delimiters may be
   different if building tree; return new [*name, *nameend), or *name = NULL
   if no more; never return NULL if given NULL.

   From Bnextname onwards, the functions follow dcutfnp. They look like
   void ?nextname(char **name, char **nameend, char *whole, Strpair *list).
   Otherwise they are the same except they can return NULL if given NULL. */

void rnextname(char **name, char **nameend, char *whole, logical build) {
  if (*name == NULL) {
    *name = whole;
    if ((*nameend = strchr(whole + 1, '?')) == NULL)
      *nameend = strchr(whole, '\0');
  }
  else if (IS_EMPTY_STRING(*nameend))
    *name = NULL;
  else {
    *name = *nameend + 1;
    *nameend = strchr(*name, '\0');
  }
}

void inextname(char **name, char **nameend, char *whole, logical build) {
  static char *s = "/";
  logical first = FALSE;

  if (*name == NULL) {
    *name = whole;
    first = TRUE;
  }
  else if (IS_EMPTY_STRING(*nameend) || IS_EMPTY_STRING((*nameend) + 1) ||
	   **nameend == '?' || *((*nameend) + 1) == '?') {
    *name = NULL;
    return;
  }
  else
    *name = *nameend + 1;

  for (*nameend = *name + (ptrdiff_t)(first && **name != '\0');
       **nameend != '/' && **nameend != '\0' && **nameend != '?'; (*nameend)++)
    ;  /* run nameend to next '/', '\0' or '?' */
  if (**nameend == '/') {
    for ( ; *((*nameend) + 1) == '/'; (*nameend)++)
      ;  /* run to last consecutive '/' */
  }
  if ((**nameend == '\0' || **nameend == '?') && !build) {
    if (first && **name != '/') {
      *name = s;
      *nameend = s + 1;
    }
    else if (first)
      *name = *nameend;
    else
      *name = NULL;
  }
}

void onextname(char **name, char **nameend, char *whole, logical build) {
  static char *s0 = "\f";
  static char *s1 = "\b";
  static logical isnum = FALSE;

  if (*name == NULL) {
    isnum = FALSE;
    *nameend = strchr(whole, '\0');
    if (!build) {
      if (strchr(whole, '.') == NULL) {
	*name = s1;
	*nameend = s1 + 1;
	debug('V', "%s", whole);
	return;
      }
      if (ISDIGIT(*(*nameend - 1))) {
	*name = s0;
	*nameend = s0 + 1;
	isnum = TRUE;
	return;
      }
    }
    else if (ISDIGIT(*whole) &&  /* test for num. more subtle while building */
	     (ISDIGIT(*(*nameend - 1)) || *(*nameend - 1) == '*')) {
      *name = s0;
      *nameend = s0 + 1;
      isnum = TRUE;
      return;
    }
    for (*name = *nameend - 1; **name != '.' && *name != whole; (*name)--)
      ;
    if (**name == '.')
      (*name)++;
  }
  else if (isnum) {
    if (*name == s0)
      *name = whole;
    else if (IS_EMPTY_STRING(*nameend) || IS_EMPTY_STRING((*nameend) + 1)) {
      *name = NULL;
      return;
    }
    else
      *name = *nameend + 1;
    for (*nameend = *name; **nameend != '.' && **nameend != '\0'; (*nameend)++)
      ;   /* run to first '.' or '\0' */
  }
  else if (*name == s1 || *name - whole < 2)
    *name = NULL;
  else {
    *nameend = *name - 1;
    for (*name -= 2; **name != '.' && *name != whole; (*name)--)
      ;
    if (**name == '.')
      (*name)++;
  }
}

void tnextname(char **name, char **nameend, char *whole, logical build) {
  logical first = FALSE;

  if (*name == NULL) {
    if ((*nameend = strchr(whole, '?')) == NULL)
      *nameend = strchr(whole, '\0');
    first = TRUE;
  }
  else if (*name == whole || *name == whole + 1 || **name == '/') {
    *name = NULL;
    return;
  }
  else
    *nameend = *name - 1;
  *name = *nameend - 1;
  if (**name == '/' && first)
    return;
  for ( ; **name != '.' && **name != '/' && *name != whole; (*name)--)
    ;
  if (**name == '.')
    (*name)++;
  else if (build)
    return;
  else if (first) {
    *name = whole;
    *nameend = whole;
  }
  else
    *name = NULL;
}

void snextname(char **name, char **nameend, char *whole, logical build) {
  if (*name == NULL) {
    *name = whole;
    for (*nameend = *name; **nameend != ':' && **nameend != '\0'; (*nameend)++)
      ;
    if (*nameend == '\0') {
      *nameend = *name;
      return;
    }
    (*nameend)++;
    if (**nameend == '/')
      (*nameend)++;
    if (**nameend == '/')
      (*nameend)++;
    for ( ; **nameend != '/' && **nameend != '\0'; (*nameend)++)
      ;
  }
  else
    inextname(name, nameend, whole, build);
}

void Znextname(char **name, char **nameend, char *whole, logical build) {
  static char *s0 = "\f";
  static char *s1 = "\b";
  static char *s2 = "\v";
  extern Strpair **domlevels;

  Strpair *ap;
  unsigned int c;

  if (*name == NULL) {
    *nameend = strchr(whole, '\0');
    if (ISDIGIT(*(*nameend - 1))) {
      *name = s0;
      *nameend = s0 + 1;
      return;
    }
    for (*name = *nameend; **name != '.' && *name != whole; (*name)--)
      ;
    if (*name == whole) {
      if (!build) {
	*name = s1;
	*nameend = s1 + 1;
      }
      return;
    }
    (*name)++;
    c = 26 * ((int)(**name - 'a'));
    if (**name != '\0')
      c += (int)(*((*name) + 1) - 'a');
    if (c >= DOMLEVEL_NUMBER)
      c = DOMLEVEL_NUMBER - 1;
    for (ap = domlevels[c]; ap != NULL && !STREQ(*name, ap->name);
	 TO_NEXT(ap))
      ;
    if (ap == NULL) {  /* domain not found */
      *name = s2;
      *nameend = s2 + 1;
      return;
    }
    /* otherwise we've now found the right number of levels */
    (*name)--;
    for (c = (unsigned int)(*(ap->data) - '0'); c > 1 && *name != whole;
	 c--) {
      for((*name)--; **name != '.' && *name != whole; (*name)--)
	;
    }
    if (*name == whole && (unsigned int)(*(ap->data) - '0') - c >= 2) {
      /* don't use whole name even if <= levels (but don't just use domain) */
      for ( ; **name != '.'; (*name)++)
	;
    }
    if (**name == '.')
    (*name)++;
  }
  else
    onextname(name, nameend, whole, build);
}

void bnextname(char **name, char **nameend, char *whole, logical build) {

  if (*name == NULL) {
    *name = whole;
    for (*nameend = *name; **nameend != '/' && **nameend != '\0';
	 (*nameend)++)
      ;
  }
  else if (**nameend == '/') {
    *name = *nameend + 1;
    for (*nameend = *name; **nameend != '.' && **nameend != ' ' &&
	   **nameend != '\0'; (*nameend)++)
      ;
  }
  else if (**nameend == '.') {
    *name = *nameend + 1;
    for (*nameend = *name; **nameend != ' ' && **nameend != '\0'; (*nameend)++)
      ;
  }
  else
    *name = NULL;
}

void pnextname(char **name, char **nameend, char *whole, logical build) {

  if (*name == NULL) {
    *name = whole;
    for (*nameend = *name; **nameend != ':' && **nameend != '\0';
	 (*nameend)++)
      ;
  }
  else if (**nameend == ':') {
    *name = *nameend + 1;
    for (*nameend = *name; **nameend != '\0'; (*nameend)++)
      ;
  }
  else
    *name = NULL;
}

void Bnextname(char **name, char **nameend, char *whole, Strpair *list) {
  /* NB "list" is ignored */
  static char *s1 = "Mozilla (compatible)";
  static char *s2 = "Mosaic";
  char *temps;

  if (*name == NULL) {
    if (strstr(whole, "osaic") != NULL) {
      *name = s2;
      *nameend = s2 + 6;
    }
    else if (headmatch(whole, "Mozilla")) {
      /* try & do something nearly sensible for all the Moz. (compatible)'s */
      if ((temps = strstr(whole + 8, "WebTV")) != NULL ||
	  (temps = strstr(whole + 8, "Opera")) != NULL) {
	*name = temps;

	if (*(temps + 5) == '\0')
	  *nameend = temps + 5;
	else for (*nameend = temps + 6; ISALNUM(**nameend) || **nameend == '.';
		  (*nameend)++)
	  ;
      }
      else if ((temps = strstr(whole + 8, "MSIE")) != NULL) {
	*name = temps;
	if (*(temps + 4) == '\0')
	  *nameend = temps + 4;
	else for (*nameend = temps + 5; ISALNUM(**nameend) || **nameend == '.';
		  (*nameend)++)
	  ;
      }
      else if (strstr(whole + 9, "ompatible")) {
	*name = s1;
	*nameend = s1 + 20;
      }
      else {  /* probably genuine Mozilla */
	*name = whole;
	*nameend = strchr(whole + 7, ' ');
	if (*nameend == NULL)
	  *nameend = strchr(whole + 7, '\0');
      }
    }
    else {
      *name = whole;
      *nameend = strchr(whole, '\0');
    }
  }
  else
    *name = NULL;
}

void Pnextname(char **name, char **nameend, char *whole, Strpair *list) {
  /* list is ignored again */
  char *c;

  if (*name == NULL) {
    if (headmatch(whole, "Mozilla"))
	whole += 7;  /* just to save searching time */
    if ((c = strstr(whole, "Windows")) != NULL) {
      c += 7;
      if (*c == ' ')
	c++;
    }
    else if ((c = strstr(whole, "Win")) != NULL &&
	     (ISDIGIT(*(c + 3)) || *(c + 3) == 'N'))
      c += 3;
    if (c != NULL) {
      if (*c == '9' && *(c + 1) == '5')
	  *name = "Windows:Windows 95";
      else if (*c == '9' && *(c + 1) == '8') {
	if (strstr(c, "Win 9x 4.9"))
	  *name = "Windows:Windows Me";
	else
	  *name = "Windows:Windows 98";
      }
      else if (*c == 'N' && *(c + 1) == 'T') {
	if (*(c + 2) == ' ' && *(c + 3) == '5')
	  *name = "Windows:Windows 2000";
	else
	  *name = "Windows:Windows NT";
      }
      else if (*c == 'C' && *(c + 1) == 'E')
	*name = "Windows:Windows CE";
      else if (*c == '3' && *(c + 1) == '.' && *(c + 2) == '1')
	*name = "Windows:Windows 3.1";
      else if ((*c == '1' && *(c + 1) == '6') || strstr(c + 1, "16bit") ||
	       strstr(c + 1, "16-bit"))
	*name = "Windows:Windows 16-bit";
      else if ((*c == '3' && *(c + 1) == '2') || strstr(c + 1, "32bit") ||
	       strstr(c + 1, "32-bit"))
	*name = "Windows:Windows 32-bit";
      else
	*name = "Windows:unkwin";
    }
    else if ((c = strstr(whole, "Mac")) != NULL) {
      if (strstr(c + 3, "68"))
	*name = "Macintosh:Macintosh 68k";
      else if (strstr(c + 3, "PPC") || strstr(c + 3, "PowerPC"))
	*name = "Macintosh:Macintosh PowerPC";
      else
	*name = "Macintosh:unkmac";
    }
    else if (strstr(whole, "Linux") != NULL || strstr(whole, "linux") != NULL)
      *name = "Unix:Linux";
    else if (strstr(whole, "BSD") != NULL)
      *name = "Unix:BSD";
    else if (strstr(whole, "SunOS") != NULL || strstr(whole, "sunos") != NULL)
      *name = "Unix:SunOS";
    else if (strstr(whole, "HP-UX") != NULL || strstr(whole, "HPUX") != NULL ||
	     strstr(whole, "hp-ux") != NULL || strstr(whole, "hpux") != NULL)
      *name = "Unix:HP-UX";
    else if (strstr(whole, "IRIX") != NULL || strstr(whole, "irix") != NULL)
      *name = "Unix:IRIX";
    else if (strstr(whole, "AIX") != NULL || strstr(whole, "aix") != NULL)
      *name = "Unix:AIX";
    else if (strstr(whole, "OSF1") != NULL)
      *name = "Unix:OSF1";
    else if (strstr(whole, "VMS") != NULL)
      *name = "OpenVMS";
    else if (strstr(whole, "X11") != NULL)
      *name = "Unix:unkux";
    else if (strstr(whole, "WebTV") != NULL)
      *name = "WebTV";
    else if (strstr(whole, "OS/2") != NULL)
      *name = "OS/2";
    else if (strstr(whole, "BeOS") != NULL)
      *name = "BeOS";
    else if (strstr(whole, "RISC OS") != NULL)
      *name = "RISC OS";
    else if (strstr(whole, "Amiga") != NULL)
      *name = "Amiga";
    else
      *name = "unkos";
    *nameend = strchr(*name, '\0');
  }
  else
    *name = NULL;
}

void Nnextname(char **name, char **nameend, char *whole, Strpair *list) {
  Strpair *al;
  char *c, *d;
  logical done;

  if (*name == NULL) {
    if ((c = strchr(whole, '?')) != NULL && *(c + 1) != '\0') {
      *c = '\0';
      d = c;
      for (al = list, done = FALSE; al != NULL && !done; TO_NEXT(al)) {
	if (MATCHES(whole, al->name)) {  /* find right engine in list */
	  for (c = d; c != NULL && !done; c = strchr(c, '&')) {
	    c++;                            /* find right arg */
	    if (headmatch(c, al->data) && *(c + strlen(al->data)) == '=') {
	      done = TRUE;
	      *name = c + strlen(al->data) + 1;
	      for (*nameend = *name; **nameend != '&' && **nameend != '\0';
		   (*nameend)++)
		;  /* run to next & */
	      if (*name == *nameend)
		name = NULL;
	    }
	  }
	}
      }
      *d = '?';
    }
  }
  else
    *name = NULL;
}

void nnextname(char **name, char **nameend, char *whole, Strpair *list) {

  if (*name == NULL)
    Nnextname(name, nameend, whole, list);   /* to find right arg etc. */
  else if (**nameend == '&' || **nameend == '\0')
    *name = NULL;
  else
    *name = *nameend + 1;

  if (*name == NULL)
    return;

  for ( ; **name == '+' || **name == ',' || **name == ';' || **name == '"' ||
	  **name == '(' || **name == ')' || **name == '.' || ISSPACE(**name) ||
	  (**name == '-' && (*(*name + 1) == '+' || *(*name + 1) == ',' ||
			     *(*name + 1) == ';' || *(*name + 1) == '"' ||
			     *(*name + 1) == '(' || *(*name + 1) == ')' ||
			     *(*name + 1) == '.' || *(*name + 1) == '-' ||
			     *(*name + 1) == '&' || *(*name + 1) == '\0' ||
			     ISSPACE(*(*name + 1))));
	(*name)++)
    ;  /* run to first wanted character; cf list in do_aliasN() and below */
       /* NB 'good' dots never occur at beginning of word */
  if (**name == '&' || **name == '\0')
    *name = NULL;
  else for (*nameend = *name; **nameend != '+' && **nameend != '&' &&
	      **nameend != '\0' && **nameend != '"' && **nameend != ',' &&
	      **nameend != ';' && **nameend != '(' && **nameend != ')' &&
	      (**nameend != '.' || (ISALNUM(*(*nameend - 1)) &&
				    ISALNUM(*(*nameend + 1)))) &&
	      (**nameend != '-' || *nameend == *name) && !ISSPACE(**nameend);
	    (*nameend)++)
      ;  /* run to first unwanted character; see above */
}
