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

 Permission is granted to use, copy, modify, or distribute any part of
 this software but only under the conditions described in the ShapeTools 
 Public License. A copy of this license is supposed to have been given
 to you along with ShapeTools in a file named LICENSE. Among other
 things, this copyright notice and the Public License must be
 preserved on all copies.
*/
/*
 * AtFStk -- Attribute Filesystem Toolkit Library
 *
 * bind.c -- AtFS toolkit library (Version Binding)
 *
 * Author: Andreas Lampen (Andreas.Lampen@cs.tu-berlin.de)
 *
 * $Header: bind.c[7.1] Thu Aug  4 16:05:12 1994 andy@cs.tu-berlin.de frozen $
 */

#include <ctype.h>
#include "atfs.h"
#include "sttk.h"
#include "atfstk.h"
#include "bind.h"

EXPORT int  atBindError;
EXPORT char atBindErrorMsg[256];

EXPORT int  atBindTrace = FALSE;
EXPORT int  atBindNoMsg = FALSE;

extern BindBaseline     bindExact, bindBefore, bindSince;

/*==========================
 *  atScanBinding
 *==========================*/

LOCAL int getNumber (ptr, digits)
     char *ptr;
     int digits;
{
  int result=0, i;

  if (!digits)
    return (AF_NOVNUM);

  for (i = 0; i < digits; i++)
    result = (result*10)+(ptr[i]-48);

  return (result);
}

EXPORT int atScanBinding (binding, str, genNo, revNo, date)
     char   *binding;
     char   **str;
     int    *genNo;
     int    *revNo;
     time_t *date;
     /* scan version binding
      * '{digit}*.{digit}*' is interpreted as version number,
      * '<anyString>:' is interpreted as rule name,
      * '<anyOtherString>' as version number alias (symbolic name),
      * '<ruleName>' as rule name (with an optional colon) and
      * NULL, empty string, :, [], [.], [:], [, [: as default version binding.
      * Square brackets around the string are ignore.
      * RETURN: type of version binding (see above)
      */
{
  char *resultStr, *startPtr, *ptr, *auxPtr;
  int  genDigits=0, revDigits=0, cacheKeyLen;

  *str = NULL;

  if (!binding)
    return (AT_BIND_DEFAULT); /* binding is NULL */

  if (*binding == '[')
    startPtr = ptr = binding+1;
  else
    startPtr = ptr = binding;
  
  /* determine format */
  while (isdigit(*ptr)) { ptr++; genDigits++; }

  if (*ptr == '*') ptr++; /* handles [*.<num>] */
  if (*ptr == '.') {
    ptr++;
    auxPtr = ptr;
    while (isdigit(*ptr)) { ptr++; revDigits++; }
    if (*ptr == '*') ptr++; /* handles [<num>.*] */
    if (*ptr == '.') {
      /* hmm, seems to be a cache key or a date (German format) */
      ptr++;
      cacheKeyLen = genDigits + revDigits + 2;
      while (isdigit(*ptr)) { ptr++; cacheKeyLen++; }
      if (*ptr == ']')
	ptr++;
      if (!*ptr) {
      /* ok, got everything -- seems to be a cache key or a German date */
	/* check date first */
	if (date && (*date = stMktime (startPtr)))
	  return (AT_BIND_DATE);
	if ((resultStr = malloc (cacheKeyLen + 1)) == NULL) {
	  atBindError = TRUE;
	  strcpy (atBindErrorMsg, "not enough memory");
	  return (AT_BIND_DEFAULT);
	}
	strncpy (resultStr, startPtr, cacheKeyLen);
	resultStr[cacheKeyLen] = '\0';
	*str = resultStr;
	return (AT_BIND_CACHEKEY);
      }
    }
    else {
      if (*ptr == ']')
	ptr++;
      if (!*ptr) {
	/* ok, got everything -- seems to be a version number */
	*genNo = getNumber (startPtr, genDigits);
	*revNo = getNumber (auxPtr, revDigits);
	if ((*genNo == AF_NOVNUM) && (*revNo == AF_NOVNUM))
	  return (AT_BIND_DEFAULT); /* binding is "[.]" */
	else
	  return (AT_BIND_VNUM);
      }
    }
  }

  if (!strncmp (startPtr, "*]", 5) || !strcmp (startPtr, "*")) {
    *genNo = AF_NOVNUM;
    *revNo = AF_NOVNUM;
    return (AT_BIND_VNUM);
  }

  if (!strncmp (startPtr, "busy]", 5) || !strcmp (startPtr, "busy")) {
    *genNo = AF_BUSYVERS;
    *revNo = AF_BUSYVERS;
    return (AT_BIND_VNUM);
  }

  /* else assume alias or rule name */
  if ((resultStr = malloc (strlen (binding)+1)) == NULL) {
      atBindError = TRUE;
      strcpy (atBindErrorMsg, "not enough memory");
      return (AT_BIND_DEFAULT);
    }
  resultStr[0] = '\0';
  *str = resultStr;

  ptr = startPtr;
  auxPtr = resultStr;
  while (*ptr) *auxPtr++ = *ptr++;
  if ((ptr > startPtr ) && (*(ptr-1) == ']'))
    *--auxPtr = '\0';
  else
    *auxPtr = '\0';

  if (!resultStr[0] || ((resultStr[0] == ':') && !resultStr[1]))
    return (AT_BIND_DEFAULT); /* binding is "[]", "[:]", "[" or "[:" */

  /* eliminate escape chars */
  auxPtr = ptr = resultStr;
  while (*ptr) {
    if (*ptr == '\\')
      ptr++;
    *auxPtr++ = *ptr++;
  }
  *auxPtr = *ptr;

  if ((ptr > resultStr) && (*(ptr-1) == ':')) {
    *(ptr-1) = '\0';
    return (AT_BIND_RULE);
  }

  if (date && (*date = stMktime (resultStr)))
    return (AT_BIND_DATE);

  return (AT_BIND_ALIAS);
}

/*=======================
 *  narrowSet
 *=======================*/

#define NARROW_SINCE 0
#define NARROW_BEFORE 1

LOCAL void narrowSet (set, baselinePtr, narrowMode)
     Af_set       *set;
     BindBaseline *baselinePtr;
     int          narrowMode;
{
  int setSize = af_nrofkeys (set), i=0;
  time_t discrDate = (time_t)0, tmpDate;
  Af_key tmpKey;
  Af_attrs attrBuf;
  Af_set   baselineSet;

  af_initattrs (&attrBuf);
  af_initset (&baselineSet);

  switch (baselinePtr->baselineType) {
  case AT_BIND_DATE:
    discrDate = baselinePtr->baselineDate;
    break;
  case AT_BIND_VNUM:
    attrBuf.af_gen = baselinePtr->baselineGen;
    attrBuf.af_rev = baselinePtr->baselineRev;
    af_subset (set, &attrBuf, &baselineSet);
    if (af_nrofkeys (&baselineSet) == 1) { 
      af_setgkey (&baselineSet, 0, &tmpKey);
      if (af_retnumattr (&tmpKey, AF_ATTSTATE) == AF_BUSY)
	discrDate = af_rettimeattr (&tmpKey, AF_ATTMTIME);
      else
	discrDate = af_rettimeattr (&tmpKey, AF_ATTSTIME);
      af_dropkey (&tmpKey);
    }
    else {
      discrDate = 0;
    }
    af_dropset (&baselineSet);
    break;
  case AT_BIND_ALIAS: {
    char aliasAttr[AT_MAXALIASLEN];
    sprintf (aliasAttr, "%s=%s", AT_ATTALIAS, baselinePtr->baselineString);
    attrBuf.af_udattrs[0] = aliasAttr;
    attrBuf.af_udattrs[1] = NULL;
    af_subset (set, &attrBuf, &baselineSet);
    if (af_nrofkeys (&baselineSet) == 1) { 
      af_setgkey (&baselineSet, 0, &tmpKey);
      if (af_retnumattr (&tmpKey, AF_ATTSTATE) == AF_BUSY)
	discrDate = af_rettimeattr (&tmpKey, AF_ATTMTIME);
      else
	discrDate = af_rettimeattr (&tmpKey, AF_ATTSTIME);
      af_dropkey (&tmpKey);
    }
    else {
      discrDate = 0;
    }
    af_dropset (&baselineSet);
    break;
  }
  } /* switch */

  while (i < setSize) {
    af_setgkey (set, i, &tmpKey);
    /* if version is a busy version compare modification date,
     * otherwise compare save date
     */
    if (af_retnumattr (&tmpKey, AF_ATTSTATE) == AF_BUSY)
      tmpDate = af_rettimeattr (&tmpKey, AF_ATTMTIME);
    else
      tmpDate = af_rettimeattr (&tmpKey, AF_ATTSTIME);

    if (narrowMode == NARROW_BEFORE) {
      if ((discrDate == 0) || (tmpDate >= discrDate)) {
	af_setrmkey (set, &tmpKey);
	setSize--;
	continue;
      }
    }
    else {
      if ((discrDate == 0) || (tmpDate <= discrDate)) {
	af_setrmkey (set, &tmpKey);
	setSize--;
	continue;
      }
    }
    af_dropkey (&tmpKey);
    i++;
  }
}
  
/*=======================
 *  bindByDate
 *=======================*/

LOCAL int bindByDate (reqAttrs, date, resultSet)
     Af_attrs *reqAttrs;
     time_t   date;
     Af_set   *resultSet;
{
  int    versCount, i, hitCount;
  Af_set hitSet;
  Af_key curKey, prevKey;

  af_initset (resultSet);

  if ((versCount = af_find (reqAttrs, &hitSet)) < 0) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, af_errmsg ("bindByDate"));
    return (-1);
  }

  if (versCount == 0)
    return (0);

  /* check exact match */
  for (i=0; i<versCount; i++) {
    if (af_setgkey (&hitSet, i, &curKey) < 0) {
      atBindError = TRUE;
      strcpy (atBindErrorMsg, af_errmsg ("bindByDate"));
      return (-1);
    }
    if (date == af_rettimeattr (&curKey, AF_ATTMTIME))
      af_setaddkey (resultSet, AF_LASTPOS, &curKey);
    af_dropkey (&curKey);
  }
  /* found exact match(es) ? */
  if ((hitCount = af_nrofkeys (resultSet)) > 0)
    return (hitCount);

  /* arrange set (sort by modification date) */
  af_sortset (&hitSet, AF_ATTMTIME);

  af_setgkey (&hitSet, 0, &prevKey);

  for (i=1; i<versCount; i++) {
    af_setgkey (&hitSet, i, &curKey);

    if ((date > af_rettimeattr (&prevKey, AF_ATTMTIME)) &&
	(date < af_rettimeattr (&curKey, AF_ATTMTIME)) )
      af_setaddkey (resultSet, AF_LASTPOS, &prevKey);
    af_dropkey (&prevKey);
    prevKey = curKey;
  }
  if (date > af_rettimeattr (&curKey, AF_ATTMTIME))
    af_setaddkey (resultSet, AF_LASTPOS, &curKey);
  af_dropkey (&prevKey);
  af_dropset (&hitSet);
  return (af_nrofkeys (resultSet));
}

/*=======================
 *  bindByRule
 *=======================*/

LOCAL void showHitSet (hitSet)
     Af_set *hitSet;
{
  int i, len, termWidth = stGetTermWidth (1), hitCount = af_nrofkeys (hitSet);  Af_key tmpKey;

  stLog ("\n\t-> Hit Set:", ST_LOG_MSG | ST_LOG_NONL);
  len = 19;

  for (i = 0; i < hitCount; i++) {
    if ((termWidth - len) < 10) {
      stLog ("\n\t           ", ST_LOG_MSG | ST_LOG_NONL);
        len = 19;
    }
    af_setgkey (hitSet, i, &tmpKey);
    if (af_retnumattr (&tmpKey, AF_ATTSTATE) == AF_BUSY) {
      stLog (" [busy]", ST_LOG_MSG | ST_LOG_NONL);
      len += 7;
    }
    else if (af_retnumattr (&tmpKey, AF_ATTSTATE) == AF_DERIVED) {
      sprintf (stMessage, " [%d.%d,cached]", af_retnumattr (&tmpKey, AF_ATTGEN), af_retnumattr (&tmpKey, AF_ATTREV));
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
      len += strlen (stMessage);
    }
    else {
      sprintf (stMessage, " [%d.%d]", af_retnumattr (&tmpKey, AF_ATTGEN), af_retnumattr (&tmpKey, AF_ATTREV));
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
      len += strlen (stMessage);
    }
    af_dropkey (&tmpKey);
  }
  stLog ("", ST_LOG_MSG);
}

LOCAL int bindByRule (reqAttrs, ruleName, mode, cache, resultSet)
     Af_attrs *reqAttrs;
     char *ruleName;
     int mode;  /* only AT_BIND_SET and AT_BIND_UNIQUE are allowed */
     int cache; /* if TRUE bind cached objects */
     Af_set *resultSet;
{
  int  altError, retCode, predPos, predCode, altCount = 1, bindHitCount;
  char *pattern, patternBuf[PATH_MAX+AT_CACHEKEYLEN];
  char *bindPtr = NULL, *arg1, *arg2, bindTargName[NAME_MAX+1];
  char *reMsg, *predName;
  Af_set *tmpSet, bindHitSet;
  Af_key *tmpKey;

  atBindInitRules ();

  if (reqAttrs->af_syspath[0]) {
    if (!strcmp (reqAttrs->af_syspath, "/"))
      sprintf (bindTargName, "/%s", reqAttrs->af_name);
    else
      sprintf (bindTargName, "%s/%s", reqAttrs->af_syspath, reqAttrs->af_name);
  }
  else
    strcpy (bindTargName, reqAttrs->af_name);
  if (reqAttrs->af_type[0]) {
    strcat (bindTargName, ".");
    strcat (bindTargName, reqAttrs->af_type);
  }

  af_initset (&bindHitSet);

  switch (atBindSetRule (ruleName, bindTargName, TRUE)) {
  case -2:
    /* rule not found */
    return (0);
  case -1:
    /* error during execution */
    return (-1);
  case 0:
    /* ok */
    break;
  default:
    atBindUnsetRule (TRUE);
    atBindError = TRUE;
    strcpy (atBindErrorMsg, "invalid rule (syntax error in rule)");
    return (-1);
  }

  while ((pattern = atBindRuleAlt (&bindHitSet))) {
    /* check if pattern is a network path name -- if not,
       nothing happens to "pattern" during "atLocalPath" */
    pattern = atLocalPath (pattern);

    /* copy pattern to a local buffer */
    strcpy (patternBuf, pattern);
    pattern = patternBuf;

    /* strip off version binding (if there is one) */
    if ((bindPtr = strrchr (pattern, '[')))
      *bindPtr = '\0';

    /* do trace output */
    if (atBindTrace) {
      sprintf (stMessage, "    %d)\t%s - pattern: %s (%s)", altCount++, atCurRuleName(), pattern, stConvertPattern (pattern));
      stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
    }

    /* check if given name matches pattern (with binding stripped off) */
    if ((pattern[0] != '*') || pattern[1]) {
      if ((reMsg = re_comp (stConvertPattern (pattern)))) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, "re_comp: ");
	strcat (atBindErrorMsg, reMsg);
	atBindUnsetRule (TRUE);
	return (-1);
      }
      if (!re_exec (bindTargName)) {
	/* pattern did not match, assume version binding to be part of pattern */
	if (bindPtr) {
	  *bindPtr = '[';
	  bindPtr = NULL;
	  reMsg = re_comp (stConvertPattern (pattern));
	  if (!re_exec (bindTargName)) {
	    /* not found in both tries */
	    if (atBindTrace) {
	      af_initset (&bindHitSet);
	      showHitSet (&bindHitSet);
	    }
	    continue;
	  }
	  /* else: found in second try */
	}
	else {
	  /* not found during first try and there was no version binding */
	  if (atBindTrace) {
	    af_initset (&bindHitSet);
	    showHitSet (&bindHitSet);
	  }
	  continue;
	}
      }
      /* else: found in first try */
    }
    /* else: pattern is '*' */

    if (bindPtr) {
      bindPtr++;
      if (cache)
	tmpSet = atBindCache (bindTargName, bindPtr);
      else
	tmpSet = atBindSet (bindTargName, bindPtr, AT_BIND_SET);
      if (tmpSet)
	af_copyset (tmpSet, &bindHitSet);
      else
	af_initset (&bindHitSet);
      bindHitCount = af_nrofkeys (&bindHitSet);
    }
    else {
      /* no version binding necessary */
      if (cache)
	bindHitCount = af_cachefind (reqAttrs, &bindHitSet);
      else
	bindHitCount = af_find (reqAttrs, &bindHitSet);
      if (bindHitCount  == -1) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("bindByRule"));
	atBindUnsetRule (TRUE);
	return (-1);
      }
    }

    if (atBindTrace)
      showHitSet (&bindHitSet);
    
    altError = FALSE;
    while ((predPos = atBindRulePred (&arg1, &arg2, &bindHitSet)) != -1) {
      predCode = atBindPredCode (predPos);
      predName = atBindPredName (predPos);
      switch (predCode)
	{
	case BIND_PRED_ATTR:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrAbsolute (&bindHitSet, arg1, arg2);
	  break;
	case BIND_PRED_ATTREX:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrAbsolute (&bindHitSet, arg1, NULL);
	  break;
	case BIND_PRED_ATTRGE:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrRelative (&bindHitSet, arg1, arg2, predCode);
	  break;
	case BIND_PRED_ATTRGT:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrRelative (&bindHitSet, arg1, arg2, predCode);
	  break;
	case BIND_PRED_ATTRLE:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrRelative (&bindHitSet, arg1, arg2, predCode);
	  break;
	case BIND_PRED_ATTRLT:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrRelative (&bindHitSet, arg1, arg2, predCode);
	  break;
	case BIND_PRED_ATTRNOT:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrRelative (&bindHitSet, arg1, arg2, predCode);
	  break;
	case BIND_PRED_ATTRMAX:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrMinMax (&bindHitSet, arg1, predCode);
	  break;
	case BIND_PRED_ATTRMIN:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  bindHitCount = atBindAttrMinMax (&bindHitSet, arg1, predCode);
	  break;
	case BIND_PRED_CONDEX:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if ((tmpSet = atBindSet (arg1, NULL, 0))) {
	    if (af_nrofkeys (tmpSet) == 0) {
	      af_dropset (&bindHitSet);
	      bindHitCount = 0;
	    }
	    af_dropset (tmpSet);
	  }
	  else {
	    af_dropset (&bindHitSet);
	    bindHitCount = 0;
	  }
	  break;
	case BIND_PRED_CONDEXPR:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if (!arg1)
	    arg1 = "sh";
	  if ((retCode = stCallCmd (arg1, arg2)) < 0) {
	    atBindError = TRUE;
	    strcpy (atBindErrorMsg, "during execution of subprocess");
	  }
	  if (retCode > 0) {
	    af_dropset (&bindHitSet);
	    bindHitCount = 0;
	  }
	  break;
	case BIND_PRED_CONDNOT:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if ((tmpSet = atBindSet (arg1, NULL, 0)) &&
	      (af_nrofkeys (tmpSet) > 0)) {
	    af_dropset (tmpSet);
	    af_dropset (&bindHitSet);
	    bindHitCount = 0;
	  }
	  break;
	case BIND_PRED_CONDUNIQ:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if ((tmpKey = atBindVersion (arg1, NULL)))
	    af_dropkey (tmpKey);
	  else {
	    af_dropset (&bindHitSet);
	    bindHitCount = 0;
	  }
	  break;
	case BIND_PRED_CONFIRM:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s,%s)", predName, NOTNIL(arg1), NOTNIL(arg2));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if (!stAskConfirm (arg1, arg2)) {
	    af_dropset (&bindHitSet);
	    bindHitCount = 0;
	  }
	  break;
	case BIND_PRED_RULE:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  switch (atBindSetRule (arg1, bindTargName, FALSE)) {
	  case -2:
	    if (atBindTrace)
	      stLog (" -- not found", ST_LOG_MSG | ST_LOG_NONL);
	    break;
	  case -1:
	    /* error during execution */
	    break;
	  case 0:
	    /* ok */
	    break;
	  default:
	    atBindUnsetRule (FALSE);
	    atBindError = TRUE;
	    strcpy (atBindErrorMsg, "invalid rule (syntax error in rule)");
	    break;
	  }
	  /* switch to another rule */
	  af_dropset (&bindHitSet);
	  bindHitCount = 0;
	  break;
	case BIND_PRED_CUT:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG | ST_LOG_NONL);
	  }
	  if (arg1 && !atBindNoMsg) {
	    sprintf (stMessage, "%s", NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG);
	  }
	  af_dropset (&bindHitSet);
	  atBindUnsetRule (TRUE);
	  return (-1);
	case BIND_PRED_MSG:
	  if (atBindTrace) {
	    sprintf (stMessage, "\t%s (%s)", predName, NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG);
	  }
	  if (arg1 && !atBindNoMsg) {
	    sprintf (stMessage, "%s", NOTNIL(arg1));
	    stLog (stMessage, ST_LOG_MSG);
	  }
	  break;
	}
      if (atBindError) {
	if (atBindDisplayErrors) {
	  sprintf (stMessage, "in bind rule -- %s", atBindErrorMsg);
	  stLog (stMessage, ST_LOG_ERROR);
	}
	af_dropset (&bindHitSet);
	bindHitCount = 0;
	atBindError = FALSE;
      }

      if (atBindTrace)
	showHitSet (&bindHitSet);

      if (bindHitCount <= 0) /* cut processing of alternative */
	break;

    } /* end of predicate loop */

    if ( ((mode == AT_BIND_UNIQUE) && (bindHitCount == 1)) ||
	((mode == AT_BIND_SET) && (bindHitCount >=1)) ) {
      af_copyset (&bindHitSet, resultSet);
      af_dropset (&bindHitSet);
      atBindUnsetRule (TRUE);
      return (bindHitCount);
    }
    
  } /* end of alternative loop */

  af_dropset (&bindHitSet);
  atBindUnsetRule (TRUE);
  return (0);
}

/*=======================
 *  atBindVersion
 *=======================*/

EXPORT Af_key *atBindVersion (name, binding)
     char *name;
     char *binding;
     /* bind name to single unique version.
      * RETURN: key of found Aso
      */
{
  Af_key   *resultKey;
  Af_attrs reqAttrs;
  Af_set   hitSet;
  int      bindType, genNo, revNo, hitCount;
  char     *bindStr = NULL, *afPath, *afName, *afType;
  char     *bindName, aliasAttr[AT_MAXALIASLEN];
  time_t   date;

  atBindError = FALSE;
  name = atLocalPath (name);

  if ((resultKey = (Af_key *)malloc (sizeof (Af_key))) == NULL) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, "not enough memory");
    return (NULL);
  }

  if ((bindStr = strrchr (name, '[')))
    bindType = atScanBinding (bindStr, &bindName, &genNo, &revNo, &date);
  else if (binding && *binding)
    bindType = atScanBinding (binding, &bindName, &genNo, &revNo, &date);
  else 
    bindType = AT_BIND_DEFAULT;

  if ((bindType == AT_BIND_DEFAULT) && !(binding && *binding) &&
      (bindExact.baselineType != AT_BIND_DEFAULT)) {
    /* if no explicit binding is given, take unique binding from command line */
    bindType = bindExact.baselineType;
    bindName = bindExact.baselineString;
    genNo = bindExact.baselineGen;
    revNo = bindExact.baselineRev;
    date = bindExact.baselineDate;
  }

  if (bindStr) *bindStr = '\0'; /* isolate name */
  afPath = af_afpath (name);
  afName = af_afname (name);
  afType = af_aftype (name);
  
  switch (bindType) {
  case AT_BIND_DEFAULT:
    if (atBindTrace) {
      sprintf (stMessage, "  Bind unique: %s[] (default)", name);
      stLog (stMessage, ST_LOG_MSG);
    }
    if (bindStr)
      *bindStr = '[';
    if (af_getkey (afPath, afName, afType, AF_BUSYVERS, AF_BUSYVERS, resultKey) == -1)
      if (af_getkey (afPath, afName, afType, AF_LASTVERS, AF_LASTVERS, resultKey) == -1)
	return (NULL);
    return (resultKey);
  case AT_BIND_VNUM:
    if (atBindTrace) {
      sprintf (stMessage, "  Bind unique %s[%d.%d] (vnum)", name, genNo, revNo);
      stLog (stMessage, ST_LOG_MSG);
    }
    if (bindStr)
      *bindStr = '[';
    if (af_getkey (afPath, afName, afType, genNo, revNo, resultKey) == -1)
      return (NULL);
    return (resultKey);
  case AT_BIND_ALIAS:
    af_initattrs (&reqAttrs);
    sprintf (aliasAttr, "%s=%s", AT_ATTALIAS, bindName);
    reqAttrs.af_udattrs[0] = aliasAttr;
    reqAttrs.af_udattrs[1] = NULL;
    strcpy (reqAttrs.af_syspath, afPath); 
    strcpy (reqAttrs.af_name, afName);
    strcpy (reqAttrs.af_type, afType);
    if ((hitCount = af_find (&reqAttrs, &hitSet)) != 0) {
      if (atBindTrace) {
	sprintf (stMessage, "  Bind unique: %s[%s] (alias)", name, bindName);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      if (hitCount > 1) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, "version number alias (symbolic name) does not uniquely identify version");
	af_dropset (&hitSet);
	return (NULL);
      }
      else if (hitCount < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindVersion"));
	af_dropset (&hitSet);
	return (NULL);
      }
      else {
	af_setgkey (&hitSet, 0, resultKey);
	af_dropset (&hitSet);
	return (resultKey);
      }
    }
    /* no return, no break: go on with case AT_BIND_RULE if hitSet is empty */
    reqAttrs.af_udattrs[0] = NULL;
  case AT_BIND_RULE:
    if (atBindTrace) {
      sprintf (stMessage, "  Bind unique %s[%s:] (rule)", name, bindName);
      stLog (stMessage, ST_LOG_MSG);
    }
    if (bindStr)
      *bindStr = '[';
    af_initattrs (&reqAttrs);
    strcpy (reqAttrs.af_syspath, afPath); 
    strcpy (reqAttrs.af_name, afName);
    strcpy (reqAttrs.af_type, afType);
    if (bindByRule (&reqAttrs, bindName, AT_BIND_UNIQUE, FALSE, &hitSet) <= 0)
      return (NULL);
    af_setgkey (&hitSet, 0, resultKey);
    af_dropset (&hitSet);
    return (resultKey);
  case AT_BIND_DATE:
    if (atBindTrace) {
      sprintf (stMessage, "  Bind unique %s[%s] (date)", name, stWriteTime (date));
      stLog (stMessage, ST_LOG_MSG);
    }
    if (bindStr)
      *bindStr = '[';
    af_initattrs (&reqAttrs);
    strcpy (reqAttrs.af_syspath, afPath); 
    strcpy (reqAttrs.af_name, afName);
    strcpy (reqAttrs.af_type, afType);
    if (bindByDate (&reqAttrs, date, &hitSet) <= 0)
      return (NULL);
    af_setgkey (&hitSet, 0, resultKey);
    af_dropset (&hitSet);
    return (resultKey);
  case AT_BIND_CACHEKEY:
    if (atBindTrace) {
      sprintf (stMessage, "  Bind unique: %s[%s] (cache key)", name, bindName);
      stLog (stMessage, ST_LOG_MSG);
    }
    if (bindStr)
      *bindStr = '[';
    af_initattrs (&reqAttrs);
    sprintf (aliasAttr, "%s=%s", AT_ATTCACHEKEY, bindName);
    reqAttrs.af_udattrs[0] = aliasAttr;
    reqAttrs.af_udattrs[1] = NULL;
    strcpy (reqAttrs.af_syspath, afPath); 
    strcpy (reqAttrs.af_name, afName);
    strcpy (reqAttrs.af_type, afType);
    if ((hitCount = af_cachefind (&reqAttrs, &hitSet)) < 0) {
      atBindError = TRUE;
      strcpy (atBindErrorMsg, af_errmsg ("atBindVersion"));
      af_dropset (&hitSet);
      return (NULL);
    }
    if (hitCount == 0)
      return (NULL);
    else {
      af_setgkey (&hitSet, 0, resultKey);
      af_dropset (&hitSet);
      return (resultKey);
    }
  }
  
  atBindError = TRUE;
  strcpy (atBindErrorMsg, "internal error (should be never reached)");
  return (NULL);
}

/*=======================
 *  atBindSet
 *=======================*/

EXPORT Af_set *atBindSet (pattern, binding, bindMode)
     char *pattern;
     char *binding;
     int  bindMode;
     /* bind names (generated from pattern) to set of source versions
      * RETURN: set of Aso keys
      */
{
  static Af_set returnSet;
  Af_set   tmpSet, resultSet;
  Af_attrs reqAttrs;
  int      bindType, genNo, revNo, hitCount, i;
  char     *bindStr = NULL, *bindName, aliasAttr[AT_MAXALIASLEN];
  char     *namePatternPtr, **nameList;
  time_t   date;

  if (!pattern || !*pattern) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, "no name/pattern argument given (atBindSet)");
    return (NULL);
  }

  atBindError = FALSE;
  pattern = atLocalPath (pattern);
  af_initset (&resultSet);
  af_initattrs (&reqAttrs);

  /* if no explicit bind mode is given */
  if (!bindMode) {
    if (atBindModeOption)
      bindMode = atBindModeOption;
    else
      bindMode = AT_BIND_SET;
  }

  if ((bindStr = strrchr (pattern, '[')))
    bindType = atScanBinding (bindStr, &bindName, &genNo, &revNo, &date);
  else if (binding && *binding)
    bindType = atScanBinding (binding, &bindName, &genNo, &revNo, &date);
  else 
    bindType = AT_BIND_DEFAULT;

  if ((bindType == AT_BIND_DEFAULT) && !(binding && *binding) &&
      !bindStr && (bindExact.baselineType != AT_BIND_DEFAULT)) {
    /* if no explicit binding is given, take unique binding from command line */
    bindType = bindExact.baselineType;
    bindName = bindExact.baselineString;
    genNo = bindExact.baselineGen;
    revNo = bindExact.baselineRev;
    date = bindExact.baselineDate;
  }

  if (bindStr)
    *bindStr = '\0'; /* isolate pattern */

  /* ToDo: handle path patterns */
  strcpy (reqAttrs.af_syspath, af_afpath (pattern));
  /* If no path is given, assume current directory */
  if (!reqAttrs.af_syspath[0])
    getcwd (reqAttrs.af_syspath, PATH_MAX);

  /* generate List of history names from pattern */
  if ((namePatternPtr = strrchr (pattern, '/')) == NULL)
    namePatternPtr = pattern;
  else
    namePatternPtr++;
  if (!(nameList = af_histories (reqAttrs.af_syspath, stConvertPattern (namePatternPtr)))) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, af_errmsg ("atBindSet"));
    return (NULL);
  }

  i=0;
  while (nameList[i]) {
    /* ignore "." and ".." */
    if (nameList[i][0] == '.') {
      if (!nameList[i][1] || ((nameList[i][1] == '.') && !nameList[i][2])) {
	i++;
	continue;
      }
    }
    af_initset (&tmpSet);
    strcpy (reqAttrs.af_name, af_afname (nameList[i]));
    strcpy (reqAttrs.af_type, af_aftype (nameList[i]));
  
    switch (bindType) {
    case AT_BIND_DEFAULT:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind set: %s[] (default)", nameList[i]);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      if (af_find (&reqAttrs, &tmpSet) < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindSet"));
      }
      break;
    case AT_BIND_VNUM:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind set: %s[%d.%d] (vnum)", nameList[i], genNo, revNo);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      reqAttrs.af_gen = genNo;
      reqAttrs.af_rev = revNo;
      if (af_find (&reqAttrs, &tmpSet) < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindSet"));
      }
      break;
    case AT_BIND_ALIAS:
    case AT_BIND_CACHEKEY:
      sprintf (aliasAttr, "%s=%s", AT_ATTALIAS, bindName);
      reqAttrs.af_udattrs[0] = aliasAttr;
      reqAttrs.af_udattrs[1] = (char *)NULL;
      if ((hitCount = af_find (&reqAttrs, &tmpSet)) != 0) {
	if (atBindTrace) {
	  sprintf (stMessage, "  Bind set: %s[%s] (alias)", nameList[i], bindName);
	  stLog (stMessage, ST_LOG_MSG);
	}
	if (bindStr)
	  *bindStr = '[';
	if (hitCount < 0) {
	  atBindError = TRUE;
	  strcpy (atBindErrorMsg, af_errmsg ("atBindSet"));
	}
	break;
      }
      /* no break: go on with case AT_BIND_RULE if hitSet is empty */
      reqAttrs.af_udattrs[0] = NULL;
    case AT_BIND_RULE:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind set: %s[%s:] (rule)", nameList[i], bindName);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      if (bindMode == AT_BIND_UNIQUE)
	bindByRule (&reqAttrs, bindName, AT_BIND_UNIQUE, FALSE, &tmpSet);
      else
	bindByRule (&reqAttrs, bindName, AT_BIND_SET, FALSE, &tmpSet);
      break;
    case AT_BIND_DATE:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind set: %s[%s] (date)", nameList[i], stWriteTime (date));
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      bindByDate (&reqAttrs, date, &tmpSet);
      break;
    } /* switch */

    if (atBindError) {
      free (nameList);
      return (NULL);
    }

    if (!(binding && *binding)) {
      /* check -since and -before baseline if given */
      if (bindSince.baselineType != AT_BIND_DEFAULT)
	narrowSet (&tmpSet, &bindSince, NARROW_SINCE);
      if (bindBefore.baselineType != AT_BIND_DEFAULT)
	narrowSet (&tmpSet, &bindBefore, NARROW_BEFORE);
    }

    hitCount = af_nrofkeys (&tmpSet);
    if ((hitCount > 0) && 
	((bindMode == AT_BIND_LAST) || (bindMode == AT_BIND_LASTSAVED))) {
      Af_key tmpKey, tmpKey2;
      int j;
      if (bindMode == AT_BIND_LAST) {
	af_sortset (&tmpSet, AF_ATTMTIME);
	af_setgkey (&tmpSet, hitCount-1, &tmpKey);
	if (af_retnumattr (&tmpKey, AF_ATTSTATE) != AF_BUSY) {
	  /* check if there is a busy version with the same modification date */
	  time_t mtimeOfLastInSet = af_rettimeattr (&tmpKey, AF_ATTMTIME);
	  j=2;
	  while ((hitCount-j) >= 0) {
	    af_setgkey (&tmpSet, hitCount-j, &tmpKey2);
	    if (af_rettimeattr (&tmpKey2, AF_ATTMTIME) < mtimeOfLastInSet) {
	      af_dropkey (&tmpKey2);
	      break;
	    }
	    if (af_retnumattr (&tmpKey2, AF_ATTSTATE) == AF_BUSY) {
	      af_dropkey (&tmpKey);
	      af_setgkey (&tmpSet, hitCount-j, &tmpKey);
	      af_dropkey (&tmpKey2);
	    }
	    j++;
	  } /* while */
	} /* if */
	af_setaddkey (&resultSet, AF_LASTPOS, &tmpKey);
      }
      else { /* AT_BIND_LASTSAVED */
	af_sortset (&tmpSet, AF_ATTVERSION);
	af_setgkey (&tmpSet, hitCount-1, &tmpKey);
	/* add last version to result sey only if it is no busy version */
	if (af_retnumattr (&tmpKey, AF_ATTSTATE) != AF_BUSY)
	  af_setaddkey (&resultSet, AF_LASTPOS, &tmpKey);
      }
      af_dropkey (&tmpKey);
    }
    else if ((hitCount > 1) && (bindMode == AT_BIND_UNIQUE)) {
      /* uniqueness is not given, just do nothing */
      ;
    }
    else {
      /* fourth parameter "FALSE" means, that no checking for
	 duplicated elements in the result set is performed */
      if (af_union (&resultSet, &tmpSet, &resultSet, FALSE) == -1) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindSet"));
	free (nameList);
	return (NULL);
      }
    }
    af_dropset (&tmpSet);
    i++;
  } /* while */

  if (bindStr)
    *bindStr = '[';

  free (nameList);
  returnSet = resultSet;
  return (&returnSet);
}

/*=======================
 *  atBindCache
 *=======================*/

EXPORT Af_set *atBindCache (pattern, binding)
     char *pattern;
     char *binding;
     /* bind names (generated from pattern) to set of cached versions
      * RETURN: set of Aso keys
      */
{
  static   Af_set returnSet;
  Af_set   tmpSet, resultSet;
  Af_attrs reqAttrs;
  int      bindType, genNo, revNo, hitCount, i;
  char     *bindStr = NULL, *bindName, cacheKeyAttr[AT_CACHEKEYLEN];
  char     *namePatternPtr, **nameList;
  time_t   date;

  if (!pattern || !*pattern) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, "no name/pattern argument given (atBindCache)");
    return (NULL);
  }

  atBindError = FALSE;
  pattern = atLocalPath (pattern);
  af_initset (&resultSet);
  af_initattrs (&reqAttrs);

  if ((bindStr = strrchr (pattern, '[')))
    bindType = atScanBinding (bindStr, &bindName, &genNo, &revNo, &date);
  else if (binding && *binding)
    bindType = atScanBinding (binding, &bindName, &genNo, &revNo, &date);
  else 
    bindType = AT_BIND_DEFAULT;

  if ((bindType == AT_BIND_DEFAULT) && !(binding && *binding) &&
      (bindExact.baselineType != AT_BIND_DEFAULT)) {
    /* if no explicit binding is given, take unique binding from command line */
    bindType = bindExact.baselineType;
    bindName = bindExact.baselineString;
    genNo = bindExact.baselineGen;
    revNo = bindExact.baselineRev;
    date = bindExact.baselineDate;
  }

  if (bindType == AT_BIND_DATE) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, "cannot bind cached objects by date (atBindCache)");
    return (NULL);
  }

  if (bindStr)
    *bindStr = '\0'; /* isolate pattern */

  /* ToDo: handle path patterns */
  strcpy (reqAttrs.af_syspath, af_afpath (pattern));
  /* If no path is given, assume current directory */
  if (!reqAttrs.af_syspath[0])
    getcwd (reqAttrs.af_syspath, PATH_MAX);

  /* generate List of names from pattern */
  if ((namePatternPtr = strrchr (pattern, '/')) == NULL)
    namePatternPtr = pattern;
  else
    namePatternPtr++;
  if (!(nameList = af_cachenames (reqAttrs.af_syspath, stConvertPattern (namePatternPtr)))) {
    atBindError = TRUE;
    strcpy (atBindErrorMsg, af_errmsg ("atBindCache"));
    return (NULL);
  }

  i=0;
  while (nameList[i]) {
    af_initset (&tmpSet);
    strcpy (reqAttrs.af_name, af_afname (nameList[i]));
    strcpy (reqAttrs.af_type, af_aftype (nameList[i]));
  
    switch (bindType) {
    case AT_BIND_DEFAULT:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind cache: %s[] (default)", nameList[i]);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      if (af_cachefind (&reqAttrs, &tmpSet) < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindCache"));
      }
      break;
    case AT_BIND_VNUM:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind cache: %s[%d.%d] (vnum)", nameList[i], genNo, revNo);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      reqAttrs.af_gen = genNo;
      reqAttrs.af_rev = revNo;
      if (af_cachefind (&reqAttrs, &tmpSet) < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindCache"));
      }
      break;
    case AT_BIND_ALIAS:
    case AT_BIND_RULE:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind cache: %s[%s:] (rule)", nameList[i], bindName);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      bindByRule (&reqAttrs, bindName, AT_BIND_SET, TRUE, &tmpSet);
      break;
    case AT_BIND_CACHEKEY:
      if (atBindTrace) {
	sprintf (stMessage, "  Bind cache: %s[%d.%d] (cache key)", nameList[i], genNo, revNo);
	stLog (stMessage, ST_LOG_MSG);
      }
      if (bindStr)
	*bindStr = '[';
      sprintf (cacheKeyAttr, "%s=%s", AT_ATTCACHEKEY, bindName);
      reqAttrs.af_udattrs[0] = cacheKeyAttr;
      reqAttrs.af_udattrs[1] = (char *)NULL;
      if (af_cachefind (&reqAttrs, &tmpSet) < 0) {
	atBindError = TRUE;
	strcpy (atBindErrorMsg, af_errmsg ("atBindCache"));
      }
      break;
    } /* switch */

    if (atBindError) {
      free (nameList);
      return (NULL);
    }

    hitCount = af_nrofkeys (&tmpSet);
    /* fourth parameter "FALSE" means, that no checking for
       duplicated elements in the result set is performed */
    if (af_union (&resultSet, &tmpSet, &resultSet, FALSE) == -1) {
      atBindError = TRUE;
      strcpy (atBindErrorMsg, af_errmsg ("atBindCache"));
      free (nameList);
      return (NULL);
    }
    af_dropset (&tmpSet);
    i++;
  } /* while */
  
  if (bindStr)
    *bindStr = '[';

  free (nameList);
  returnSet = resultSet;
  return (&returnSet);
}
