#include <ctype.h>

#include <wh/acedb.h>

#include <wac/ac.h>
#include <wac/ac_.h>
#include <wac/acclient_.h>
#include <wac/actable_.h>

static char* ac_sys (AC_DB db) ;

BITSET_MAKE_BITFIELD 	 /*	 define bitField for bitset.h ops */

static void ac_fillobj (AC_OBJ obj) ;
static void cache_detach (struct ac_real_object *obj) ;
static AC_OBJ cache_find ( AC_DB db, int class_number , char *name, AC_HANDLE handle) ;
static AC_OBJ cache_create (AC_DB db, int class_number, char *name , AC_HANDLE handle) ;
static int active_objects_count (unsigned char *buff) ;

static void ac_dump_object (char *s, AC_OBJ obj, struct ac_real_object *robj)
{
  if (robj && strcmp (robj->name, "a") != 0)
    return ;
  if (0)  ac_dump_object (0, 0, 0) ; /* for compiler happiness */
  printf ("%-20s ", s) ;
  if (obj)
    printf ("obj %08x ", (int)obj) ;
  else
    printf ("             ") ;
  if (robj)
    {
      printf ("x %08x ref %d ", (int)robj, robj->refcount) ;
      printf ("t %08x class %02x name %s", (int)robj->table, robj->class_number, robj->name) ;
    }
  printf ("\n") ;
}

/********************************************************************/

/*
 * Here we see some interesting implications of the handle library.
 * You call ac_free () to free the object, but it doesn't appear
 * to do much of anything.  But when ac_free () calls messfree (), 
 * messfree () calls the finalization function for that data item.
 * The finalization function was registered when you created the
 * data element with halloc ().
 */


static void ac_free_real_object (struct ac_real_object *robj )
{
  cache_detach (robj) ;
  ac_free (robj->table) ;
  messfree (robj->a_data) ;
  bitSetDestroy (robj->hasTag) ;
  arrayDestroy (robj->tagLine) ;
  /*
   * the tag_name fields in tagLine are all pointers
   * into AC_DB object data and do not need to be freed
   * by us.
   */
  /* robj->class is pointer into AC_DB object data */
  /* robj->name is pointer into AC_DB */

  robj->db = 0 ;

  messfree (robj) ;
}

static void ac_finalize_object (void *v)
{
  AC_OBJ obj = (AC_OBJ)v ;
  struct ac_real_object *x ;
  BOOL test = FALSE ;

  x = obj->x ;

  if (x == 0)
    return ; /* mieg */
  if (x == 0)
    messcrash ("finalizing AC_OBJ with NULL x pointer") ;

  test = !strcmp (ac_name(obj), "self_ref") ;
  obj->x = 0 ;

  if (x->refcount > 0)
    x->refcount-- ;
  if (test)
    printf ("ac_finalize self_ref recfount now =%d\n",x->refcount ) ;
  if (x->refcount > 0)
    return ;

  if (test)
    printf ("ac_finalize freeing self_ref\n") ;
  ac_free_real_object (x) ;
}

static void ac_finalize_keyset (void *v)
{
  AC_KEYSET ks = (AC_KEYSET)v ;
  char buf[100] ;

  ks->magic = 0 ;
  ks->x->refcount-- ;
  if (ks->x->refcount > 0)
    return ;

  sprintf (buf, "kclear _aks%d",  ks->x->id) ;
  ks->x->db->lazy_command (ks->x->db, buf) ;
  messfree (ks->x) ;
}


static void ac_finalize_iterator (void *v)
{
  struct ac_iter * iter = (struct ac_iter*)v ;
  int x ;

  iter->magic = 0 ;

  iter->real_keyset->refcount-- ;
  if (iter->real_keyset->refcount <= 0)
    messfree (iter->real_keyset) ;

  /*
   * must free any un-returned objects we have in the iterator
   */
  for (x = iter->ra_next ; x < iter->ra_max ; x++)
    ac_free (iter->obj[x]) ;

}

static void ac_finalize_db (void *v)
{
  int x, y, num ;
  struct ac_db *adb = (struct ac_db *) v ;
  Array real_objects[ 256] ;

  memset (real_objects, 0, 256 * sizeof (Array)) ;
  adb->magic = 0 ;
  /*
  * close the transport that talks to the database
  */
  if (adb->close_transport)
    (adb->close_transport) (adb) ;

  num = 0 ;

  /*
   * Empty the object cache.  For each class, we want to find
   * all the objects in the cache and destroy them.
   */
  for (x=0 ; x<256 ; x++)
    {
      void *key, *value ;
      if (! adb->cached_objects[x])
	continue ;

      /*
       * First:  walk through the cache and make a list of
       * all the ac_real_object structs.  You can't use
       * the associator iterator because we will be changing
       * the associator at various points in the loop.
       *
       * We increase the refcount on every object that we
       * are about to free, so we can be sure that it does
       * not get freed too early.
       */

      real_objects[x]=arrayCreate (100, struct ac_real_object * ) ;
      num = 0 ;

      key = 0 ;
      value = 0 ;
      while (uAssNext (adb->cached_objects[x], &key, &value))
	{
	  struct ac_real_object *t ;
	  t = (struct ac_real_object *)value ;
	  if (! t)
	    continue ;
	  t->refcount++ ;
	  if (t->name && !strcmp (t->name, "self_ref"))
	    printf ("db_finalize increased refcount of self_ref, now %d\n",  t->refcount) ;
	  array (real_objects[x], num++, struct ac_real_object *) = t ;
	}
    }

  for (x=0 ; x<256 ; x++)
    {
      if (!real_objects[x])
	continue ;
      /*
       * Next: free all the data tables from the cached objects.  This
       * may try to deallocate some AC_OBJ objects, but we incremented
       * all the ref counts above so nothing is really deallocated.
       */
      for (y=0 ; y < arrayMax (real_objects[x]) ; y++)
	{
	  struct ac_real_object *t ;
    	  AC_TABLE tb ;
	  t = arr (real_objects[x], y, struct ac_real_object *) ;
	  if (t && t->table)
	    {
	      /*
	       * tables point back into objects, so when you free the
	       * table, you need to make sure you can't find your way
	       * back to this object and try to free the table again.
	       */ 
	      if (t->name && !strcmp (t->name, "self_ref"))
		printf ("db_finalize fres the table of self_ref, refcount=%d\n",  t->refcount) ;
	      tb =  t->table ;
	      t->table = 0 ;
	      ac_free (tb) ;
	    }
	}
    }

  for (x=0 ; x<256 ; x++)
    {
      if (!real_objects[x])
	continue ;
      /*
       * Now we believe there are no more AC_OBJ that refer to any of
       * our struct ac_real_object -- at least none that are under our
       * control.  We can free our ac_real_object now.
       */
      for (y = 0 ;  y < arrayMax (real_objects[x]) ; y++)
	{
	  struct ac_real_object *t ;
	  t = arr (real_objects[x], y, struct ac_real_object *) ;
	  if (t)
	    {
	      t->refcount-- ;	/* undo the increment we made above */
	      if (t->name && !strcmp (t->name, "self_ref"))
		printf ("db_finalize decreased refcount of self_ref, now %d\n",  t->refcount) ;
	      ac_free_real_object (t) ;
	    }
	}

      arrayDestroy (real_objects[x]) ;

    }
  /*
   * now destroy the object cache itself - the symbol table is made
   * of a dict and an associator for each class. 
   */
  for (x=0 ; x<256 ; x++)
    {
      if (adb->cached_names[x])
	uDictDestroy (adb->cached_names[x]) ;
      if (adb->cached_objects[x])
	uAssDestroy (adb->cached_objects[x]) ;
      if (adb->flags[x])
        arrayDestroy (adb->flags[x]) ;
    }

  dictDestroy (adb->classDict) ;
  dictDestroy (adb->tag_dict) ;
}

/********************************************************************/

/*
 * Contact with the server is through 
 *  void ac_partial_command_XXX (
 *
 *	AC_DB db, 			db to contact
 *
 *	char *command, 			command to send or "encore" 
 *
 * 	unsigned char **response, 	filled in with ptr to
 *				   	response data
 *
 *	int *response_length, 		filled in with len of output 
 *					data
 *
 *	unsigned char **response_free, 	free this address when done 
 *					with the response - may not
 *					be the same as the response
 *
 *	int *encore			filled in with 0 if you got
 *					the entire response or 1 if
 *					there is more
 *	)
 *
 * The server may choose to send a partial response.  If it does, *encore
 * is set and you should call ac_partial_command again with the command
 * "encore" to get the rest of the response.
 *
 * When you are done with the response, use free () to deallocate
 * the value returned in the response_free parameter.  With some 
 * transports, the response is a pointer into the middle of the data
 * block that was allocated for the response.
 *
 * The correct partial_command function is available in 
 * AC_DB->ac_partial_command
 *
 * The following includes bring in implementations of various
 * transports.  To create a new transport, include the implementation
 * code here and modify ac_open_connection () - no other changes are
 * needed.
 */

#include "../wac/acclient_rpc.c"

#include "../wac/acclient_socket.c"

#include "../wac/acclient_acetcp.c"


#define protocol_match(p,s) (strncmp (p, s, sizeof (s)-1) == 0 && ( p[sizeof (s)-1] == 0 || isdigit ((int)p[sizeof (s)-1]) ) )
	/*
	* match protocol p to constant string s - the name of the protocol matches up to the end
	* of the string or the first digit
	*/

char * ac_open_connection (AC_DB db, char *protocol, char *host, int port, char *extras )
{
  if ( protocol_match (protocol, "rpc") || protocol_match (protocol, "r"))
    return ac_open_rpc (db, protocol, host, port) ;
  if ( protocol_match (protocol, "acetcp") || protocol_match (protocol, "a"))
    return ac_open_acetcp (db, protocol, host, port) ;
  if ( protocol_match (protocol, "socket") || protocol_match (protocol, "s"))
    return ac_open_socket (db, protocol, host, port, extras) ;
  return "000 unrecognized database specifier" ;
}

/* 
   in case of success, use the returned value as a handle for
   all transcations, 
   in case of failure, we return 0 plus an error mesage
   this error message is a fixed string, so the client
   does not have to free it and the message is not volatile 
*/

AC_DB ac_open_db (char *database, char **error)
{
  char *buf, *s = 0 ;
  int port_number ;
  struct ac_db *db = 0 ;
  char *protocol, *host, *port, *extras ;

  if (error) *error = 0 ;

  if (!database || !*database)
    {
      if (error) *error = "001 bad call to ac_open, no database name provided" ;
      return 0 ;
    }

  protocol = 0 ;
  host = 0 ;
  port = 0 ;
  extras = 0 ;

  buf = strdup (database) ;
  protocol = buf ;		/* which transport we will use */
  s = strchr (protocol, ':') ;

  if (s)
	{
	*s++ = 0 ;
	host = s ;		/* which host to contact */
	s = strchr (host, ':') ;
	}
  if (s)
	{
	*s++ = 0 ;
	port = s ;		/* numeric part of address */
	s = strchr (port, ':') ;
	}
  if (s)
	{
	*s++ = 0 ;
	extras = s ;		/* further parameters, like user/pass */
	}
  if (! port )
	{
	/*
	* This used to be for compatibility with the old RPC specifier.
	* Now it chooses a default transport.
	*/
	if (host && atoi (host))
	  {
	  port = host ;
  	  host = protocol ;
	  protocol = "a" ;
	  }
	else
	  {
	  if (error) *error = "003 badly formatted database name" ;
	  return 0 ;
	  }
	}

  port_number = atoi (port) ;

  /* we allocate the db struct and initialise it */
  /* NB: halloc sets memory to zero and never fails */
  db = (AC_DB) halloc (sizeof (struct ac_db), 0) ;
  blockSetFinalise (db, ac_finalize_db) ;

  db->magic = MAGIC_BASE + MAGIC_AC_DB ;

  s = ac_open_connection (db, protocol, host, port_number, extras ) ;  
  free (buf) ;
  if (s)
    {
      messfree (db) ;
      if (error) *error = s ;
      return NULL ;
    }

  s = ac_sys (db) ;
  if (s) /* error */
    {
      messfree (db) ;
      if (error) *error = s ;
      return NULL ;
    }

  return db ;
}

/********************************************************************/
/* general acedb command, 
 *  return zero if null parameters are provided by the user.
 *      or the database reply
 *
 *  the result is allocated on the user provided handle
 *  so the user should either messfree his handle or
 *  explicitely messfree the returned buffer
 *
 * We have to copy the result because askServerBinary uses
 * malloc () but we want halloc () so we can have a handle.
 * We should fix this so we can eliminate the copy operation.
 *
 * If the server says "encore", we collect up a list of all the responses
 * and then concatenate them at the end.  This is faster that continually
 * using realloc () to make the buffer bigger, because each realloc () is
 * likely to require a copy of all the partial response data received
 * so far.
 * 
 */

unsigned char *ac_command (AC_DB db, char *command, int *result_lengthp, 
			   AC_HANDLE handle)
{
  unsigned char *response, *response_free ;
  int  response_len ;
  int  encore ;
  struct response_record
  {
    unsigned char *data ;
    unsigned char *free ;
    int len ;
  } *t ;
  Array responses ;
  int x, y ;

  /*
   * ac_partial_command sends a command to the server and gets the
   * first block of the server's reply.
   */
  (db->ac_partial_command) (db, command, &response, &response_len, &response_free, &encore) ;

  if (! encore)
    {
      /*
       * The reply contains all the reply data.  Most responses come here.
       */
      unsigned char *s ;
      s = halloc (response_len + 1, handle) ;
      memcpy (s, response, response_len) ;
      s[response_len] = 0 ;
      free (response_free) ;
      if (result_lengthp)
	*result_lengthp = response_len ;
      return s ;
    }

  /*
   * The server told us that we will get more data if we ask for "encore".
   * Keep doing that until we get them all, then concatenate them into a
   * single buffer.
   */
  responses = arrayCreate (10, struct response_record) ;
  t = & array (responses, 0, struct response_record) ;
  t->data = response ;
  t->free = response_free ;
  t->len = response_len ;

  x = 1 ;
  while (encore)
    {
      /*
       * getting the next section of the response
       */
      (db->ac_partial_command) (db, "encore", &response, &response_len, &response_free, &encore) ;
      t = & array (responses, x, struct response_record) ;
      t->data = response ;
      t->free = response_free ;
      t->len = response_len ;
      x++ ;
    }

  /*
   * Count how big the returned buffer must be.
   */
  response_len = 0 ;
  for (x=0 ; x<arrayMax (responses) ; x++)
    response_len += array (responses, x, struct response_record).len ;
 
  response = halloc (response_len + 1, handle) ;
  response[response_len] = 0 ;
 
  /*
   * Copy all the segments of the response into the buffer.
   * Free the returned segments (with free, not messfree)
   */
  y=0 ;
  for (x=0 ; x<arrayMax (responses) ; x++)
    {
      t= & array (responses, x, struct response_record) ;
      memcpy (response+y, t->data, t->len) ;
      free (t->free) ;
      y = y + t->len ;
    }

  /*
   * clean up our overhead
   */
  arrayDestroy (responses) ;
 
  /*
   * done
   */
  if (result_lengthp)
    *result_lengthp = response_len ;

  return response ;
}


/*********************************************************************
 *********************************************************************
 *********************************************************************
 **
 ** Here is a section of code that deals with parsing the -C mode
 ** server responses.  All functions and data structures are private.
 **
 ** (struct parsing *) is a cursor that the parsing functions use for
 ** their input.
 **
 ** parse_init ( char *buffer, int len, AC_DB db)
 **	create a cursor.  buffer is the text you want to parse, and
 **	len is how long it is.  db is the database we are talking
 **	to, and is required because we have to recognize names that
 **	-C encodes as numbers and because we may need to create objects
 **
 **	These functions assume that any input you give is coming from 
 **	ac_command or ac_partial_command, and therefore it is known
 **	that strlen () used in the buffer will always find a '\0' even
 **	if the buffer is not well formed.
 **
 ** parse_getc ( struct parsing *p )
 **	returns the next byte from the input stream.  returns -1 on EOF.
 ** 
 ** parse_getstring ( struct parsing *p )
 **	returns a null terminated string read from the input.  The
 **	returned pointer points into the buffer that was passed to
 **	parse_init ()
 **
 ** parse_object_header (struct parsing *in, int *class_out, char **name_out)
 **	gets the class number and object name from the beginning of an
 **	object record.  messfree () the name.
 **
 ** parse_u (struct parsing *p, void *u)
 **	gets a 4 byte quantity, performing byte swap if necessary.  the
 **	name "u" comes from the fact that there is typically a union of
 **	int, float, etc that holds the data.  the "u" parameters is the
 **	address of where to store the data.
 **
 ** parse_table (struct parsing *p, AC_TABLE table, AC_OBJ obj)
 **	parse data into an AC_TABLE.  obj is the object that the data is
 **	going into (though you must still pass the correct table) or NULL if
 **	the table is not part of an object (i.e. tablemaker or aql results).
 **	row and col are the current row/col at the start of the parsing.
 **	
 **
 ** 
 *********************************************************************/

/*
 * this is the input descriptor for the parsing functions
 */
struct parsing
{
  unsigned char * buffer ;
  int 	len ;
  int 	cursor ;
  int	swap ;
  AC_DB	db ;
} ;

/********************************************************************/

static struct parsing *
parse_init (unsigned char *buffer, int len, AC_DB db )
{
  struct parsing *p ;
  p = halloc (len, 0) ;
  p->buffer = buffer ;
  p->len = len ;
  p->cursor = 0 ;
  p->swap = db->swap_needed ;
  p->db = db ;
  return p ;
}

/********************************************************************/

/*
 * bug: inline this, but the alpha compiler doesn't know inline
 *
static int
parse_getc (struct parsing *p)
{
  int c ;
  if (p->cursor >= p->len)
    return -1 ;
  c=p->buffer[p->cursor] ;
  p->cursor++ ;
  return c ;
}
*/
/* here is an equivalent macro */
#define parse_getc(_pp) ((_pp)->cursor >= (_pp)->len ? -1 : ((_pp)->buffer[((_pp)->cursor)++]))

/********************************************************************/

static char *
parse_getstring (struct parsing *p)
{
  int len ;
  unsigned char *text ;

  len = strlen ((char *) (p->buffer+p->cursor)) ;
  text = p->buffer+p->cursor ;
  if (p->cursor+len > p->len)
    return NULL ;
  
  p->cursor += len+1 ;
  return (char *)text ;
}

/********************************************************************/

static int
parse_object_header (struct parsing *in, int *class_out, char **name_out)
{
  int c ;
  for ( ; ;)
    {
      c = parse_getc (in) ;
      if (c == -1)
	return -1 ;
      else if (c == 'N')
	{
	  *class_out = parse_getc (in) ;
	  *name_out = parse_getstring (in) ;
	  return 0 ;
	}
      else if (c == '/')
	{
	  while ((c != -1) && (c != '\n') )
	    c = parse_getc (in) ;
	}
      else if (c == '\n')
	continue ;
      else if (c == 0)
	continue ;
      else
	return -2 ;
    }
}

/********************************************************************/

static void
parse_u (struct parsing *p, void *u)
{
  unsigned char *uu = (unsigned char *)u ;
  if (p->swap)
    {
      uu[3] = parse_getc (p) ;
      uu[2] = parse_getc (p) ;
      uu[1] = parse_getc (p) ;
      uu[0] = parse_getc (p) ;
    }
  else
    {
      uu[0] = parse_getc (p) ;
      uu[1] = parse_getc (p) ;
      uu[2] = parse_getc (p) ;
      uu[3] = parse_getc (p) ;
    }
}

/********************************************************************/

static void
parse_table (struct parsing *p, AC_TABLE table, AC_OBJ obj)
{
  int kType, n ;
  char *s ;
  union
  { int	i ;
    float f ;
    KEY   k ;
    mytime_t time ;
  } u ;
  int class_number ;
  char *name ;
  int row, col ;
  int a_data_len = -1 ;
  int in_hash ;

  /*
   * -C begins it's output assuming that your cursor is at
   * the correct row, but column == -1.  The first time you
   * call parse_table, there are no rows or columns, so you start
   * at (0, -1).  On successive calls (like you might have with
   * tablemaker encores), you begin at the max row of the table, 
   * but still at column -1.
   */
  row = table->rows ;
  col = -1 ;

  in_hash = 0 ;
  for ( ; ;)
    {
      kType = parse_getc (p) ;
      switch (kType)
	{
	case -1:	/* EOF on input string */
	case '#': /* end of data */
	  return ;

 	  /*
	  * entering or leaving a #xxx construct
	  */
	case '!':
	  in_hash++ ;
	  break ;

	case '@':
	  in_hash-- ;
	  break ;

	  /* simple data */
	case 'g': /* 4 bytes deposit tag in current cell */
	  {
	  parse_u (p, &u) ;
	  s = dictName (p->db->tag_dict, u.k ) ;
	  ac_table_insert_type (table, row, col, &s, ac_type_tag) ;

	  if ( obj && ( in_hash == 0) )
	    {
	      /*
	       * if we are loading an object, we need to remember where
	       * the tag was.  Note that we do NOT just remember the
	       * first place we see the tag!  If you are in a #xxx
	       * construct, you can see duplicate tags.
	       *
	       */
	      if ( ! bit (obj->x->hasTag, u.k) )
		{
		  /*
		   * We have not seen it before.  add it, remember
		   * where it is.
		   */
		  struct tagLine nt, *t ;
		  nt.row = row ;
		  nt.col = col ;
		  nt.tag_number = u.k ;
		  nt.tag_name = s ;
		  t = & array (obj->x->tagLine, arrayMax (obj->x->tagLine), struct tagLine) ;
		  *t = nt ;
		  bitSet (obj->x->hasTag, u.k) ;
		}
	    }
          }
	  break ;

	case 'K':
	case 'k':
   	  {
	    AC_OBJ new_obj ;
	    class_number = parse_getc (p) ;
	    name = parse_getstring (p) ;
	    new_obj = cache_find ( p->db, class_number , name, NULL ) ;
	    if (! new_obj)
	      new_obj = cache_create (p->db, class_number, name, NULL) ;
 	    if (kType == 'k' && ! new_obj->x->table)
	      {
		/*
		 * we know the object to be empty, so we fill it as empty
		 */
		new_obj->x->table = ac_empty_table (0, 0, 0) ;
		new_obj->x->filled = TRUE ;
		new_obj->x->get_date = ++(p->db->date) ;
	      }

	    ac_table_insert_type (table, row, col, &new_obj, ac_type_obj ) ;
 	    messfree (new_obj) ;
  	  }
	  break ;

	case 'i': /* 4 bytes deposit integer in current cell */
	  parse_u (p, &u) ;
	  ac_table_insert_type (table, row, col, &u, ac_type_int) ;
	  break ;

	case 'f': /* 4 bytes deposit float in current cell */
	  parse_u (p, &u) ;
	  ac_table_insert_type (table, row, col, &u, ac_type_float) ;
	  break ;

	case 'd': /* 4 bytes deposit date in current cell */
	  parse_u (p, &u) ;
	  ac_table_insert_type (table, row, col, &u, ac_type_date) ;
	  break ;

	case 'v': /* void : vNULL*/
	  /*
	   * We must insert a cell that is ac_type_empty even though
	   * that is the value already there.  By performing the
	   * insert, we also increase the size of the table.
	   */
	  ac_table_insert_type (table, row, col, &u, ac_type_empty) ;
	  break ;

	case 't': /* string "\0"	deposit text in current cell */
	  s = parse_getstring (p) ;
	  ac_table_insert_text (table, row, col, s) ;
	  break ;

	  /* geometry of the table */
	case '>': 
	  col++ ; 
	  break ;

	case '.': /* increment row */
	  row++ ;
	  ac_table_copydown (table, row, col) ;
	  break ;

	case '<': /* increment row, decrement col */
	  row++ ;
	  col-- ;
	  ac_table_copydown (table, row, col) ;
	  break ;

	case 'l': /* left: subtract %c from column.  Do not change row
		   * this is always followed by a "." that increments the row 
		   */
	  n = parse_getc (p) ;
	  col -= n ;
	  break ;

	  /* null operations */

	case '\n': /* no operation */
	  break ;

	case '/': /* acedb comment */
	  while (kType != '\n')
	    kType = parse_getc (p) ;
	  break ;

	case 'c':	
	  /*
	   * c integer
	   * tells the length of the following A data
	   */
	  parse_u (p, &u) ;
	  a_data_len = u.i ;
	  break ;

	case 'D':	/* dna */
	case 'p':	/* peptide */
	  {
	  /*
 	   * dna and peptide have the same format - a_data_len bytes of
	   * unformatted data.
 	   */

	  if (! obj)
	    messcrash ("server broken: trying to load type A data into non-object table") ;

	  if (a_data_len == -1)
	    messcrash ("server broken: sent type A data without length") ;

	  /*
	   * get some memory, copy the data in.
	   */
	  if (obj->x->a_data)
	    messfree (obj->x->a_data) ;

	  obj->x->a_data_len = a_data_len ;

	  obj->x->a_data = halloc (a_data_len+1, 0) ;
	  s = (char *) obj->x->a_data ;

 	  while (a_data_len > 0)
	    {
	      *s++ = parse_getc (p) ;
	      a_data_len -- ;
	    }
 	  *s++ = 0 ;

	  /*
	   * always have an empty table - it makes the code easier to
	   * read if we don't have to litter it with " if (x->table) ... "
	   */
  	  if (! obj->x->table)
	    obj->x->table = ac_empty_table (0, 0, 0) ;

	  /*
	   * object is now filled.  also, clear a_data_len in case the
	   * server sends another type A data block.
	   */
	  obj->x->filled = TRUE ;
	  obj->x->get_date = ++(p->db->date) ;
	  a_data_len = -1 ;

	  }
          break ;

	case 0:
	  /*
	  *  buggy servers sometimes insert a 00 byte
	  */
	  break ;

	default:
	  printf ("in parse table: %d of %d bytes\n", p->cursor, p->len) ;
	  printf ("non implemented type in wac parse_table: %d %c", kType, kType) ;
	  messcrash ("non implemented type in wac parse_table: %d %c", kType, kType) ;

	  break ;
	}
    }
}

/********************************************************************
*********************************************************************
*********************************************************************
**
** Here is a section of code that deals with the object cache.  It
** is not very elaborate, but it gets us most of the speedup of a
** cache by eliminating a server transaction each time a cached 
** object is found.
**
** The cache uses a dict and an associator to make a symbol table.
** To find an object, you look it up in the dict, then use the
** index as the key to the associator.  The resulting pointer is
** the AC_OBJ.
**
** The cache currently has no provision for keeping objects that
** have been freed recently.  As long as we continue to use this
** library primarily in CGI programs, that is probably not going
** to be a problem.
**
*********************************************************************/

void ac_db_refresh (AC_DB db)
{
  db->refresh_date = ++db->date ; /* all previously cached object are now too old */
}

/*
 * cache_find () locates the object in the cache, increments the reference
 * count, and returns it.  You must ac_free () the object after you get
 * it from cache_find ().
 */

static AC_OBJ
cache_find ( AC_DB db, int class_number, char *name, AC_HANDLE handle)
{
  struct ac_real_object *obj ;
  AC_OBJ ersatz ;
  int index ;
  void *tmp ;

  /*
   * no cached names in this class
   */
  if (! db->cached_names[class_number])
    return NULL ;

  if (! db->cached_objects[class_number] )
    return NULL ;

  /*
   * there are cached names, but not this one
   */

  if ( ! dictFind (db->cached_names[class_number], name, &index) )
    return NULL ;

  if ( ! assFind ( db->cached_objects[class_number], assVoid (index), &tmp) )
    return NULL ;

  /*
   * found it
   */
  obj = (struct ac_real_object *) tmp ;

  if (obj->get_date < db->refresh_date) /* cached object is too old */
    {
      cache_detach (obj) ;
      return NULL ;
    }

  obj->refcount++ ;

  ersatz = halloc (sizeof (struct ac_object), handle) ;
  blockSetFinalise ( ersatz, ac_finalize_object ) ;
  ersatz->magic = MAGIC_BASE + MAGIC_AC_OBJECT ;
  ersatz->x = obj ;
  if (!strcmp (ac_name(ersatz), "self_ref"))
    printf ("cache_find self_ref, refcount=%d\n",  obj->refcount) ;
  return ersatz ;
}

/*
 * cache_create () makes an empty object in the cache.  You must ac_free () 
 * the object when you are done with it.
 *
 * The name parameter is not needed after cache_create returns.
 */

static AC_OBJ
cache_create (AC_DB db, int class_number, char *name, AC_HANDLE handle )
{
  struct ac_real_object *x ;
  AC_OBJ ersatz ;
  int index ;

  /**
   ** create the object
   **/

  x = (struct ac_real_object *) halloc (sizeof (struct ac_real_object), 0) ;

  x->refcount = 1 ;
  x->db = db ;
  x->table = NULL ;
  /*
   * An empty object would have a table with 0 rows
   */
  x->a_data = NULL ;
  x->filled = FALSE ;
  x->hasTag = bitSetCreate (db->maxTag, 0) ;
  if (db->maxTag > 3000)
    abort () ;
  x->tagLine = arrayHandleCreate (8, struct tagLine, 0) ;
  x->class = dictName (db->classDict, class_number) ;
  x->class_number = class_number ;

  /**
   ** add the object to the cache
   **
   **/

  if (! db->cached_names[class_number])
    {
      /*
       * No dict present - create it.  Index 0 is not allowed in
       * associators, so we have to use it up by inserting an invalid
       * object name.
       * Do not allocate it on the db->handle
       */
      db->cached_names[class_number] = dictHandleCreate (100, 0) ;
      dictAdd ( db->cached_names[class_number], "_____", &index ) ;
    }

  dictAdd ( db->cached_names[class_number], name, &index) ;
  x->name = dictName ( db->cached_names[class_number], index) ;

  /*
   * The associator part of the symbol table.
   * Do not allocate it on the db->handle
   */
  if (! db->cached_objects[class_number] )
    db->cached_objects[class_number] = assHandleCreate (0) ;
  assInsert ( db->cached_objects[class_number], assVoid (index), (void *)x) ;

  ersatz = halloc (sizeof (struct ac_object), handle) ;
  blockSetFinalise ( ersatz, ac_finalize_object) ;
  ersatz->magic = MAGIC_BASE + MAGIC_AC_OBJECT ;
  ersatz->x = x ;
 
  if (!strcmp (ac_name(ersatz), "self_ref"))
    printf ("cache_create self_ref, refcount=%d\n",  ersatz->x->refcount) ;
  return ersatz ;
} /* cache_create */

/*
 * cache_detach () just takes it out of the cache.  It does not destroy
 * the object.
 */
static void
cache_detach (struct ac_real_object *obj)
{
  AC_DB db ;
  int index ;
  int class_number ;

  db = obj->db ;

  class_number = obj->class_number ;

  if ( ! dictFind (db->cached_names[class_number], obj->name, &index) )
    abort () ;	/* never happens */

  assRemove (db->cached_objects[class_number], assVoid (index)) ;
}

/********************************************************************/
/********************************************************************/
/********************************************************************/
/********************************************************************/
/********************************************************************/

/*********************************************************************
 *********************************************************************
 **
 ** assorted private functions 
 **
 *********************************************************************
 ********************************************************************/

/*
 * find_one () - find one object in the database and leave it in the
 * active keyset.  This is used by some of our internal functions, but
 * can never be public because the AC library does not have a concept
 * of the active keyset.
 */

static int find_one (AC_DB db, char *class, char *name)
{
  char *new_class ;
  char *new_name ;
  char *command ;
  unsigned char *response ;
  int nn ;
  
  new_class = freeprotect (class) ;
  new_class = strnew (new_class, 0) ;

  new_name = freeprotect (name) ;
  new_name = strnew (new_name, 0) ;

  command = messalloc (30 + strlen (new_class) + strlen (new_name)) ;
  if (0) sprintf (command, "query find %s IS %s\nlist -C", new_class, new_name ) ;
  else sprintf (command, "query find %s IS %s", new_class, new_name ) ;

  response = ac_command (db, command, 0, 0) ;

  nn = active_objects_count (response) ;

  messfree (response) ;
  messfree (command) ;
  messfree (new_name) ;
  messfree (new_class) ;


  return nn ;
}


/*
 * ac_fillobj () - fetch the data for an unfilled object
 *
 * ac_fillobj_part2 () - entry point for filling object when it is the
 * 	only one in the active keyset
 */
static void ac_fillobj_part2 (AC_DB db, AC_OBJ *objp, BOOL create, int class_number, char *name, AC_HANDLE handle) ;

static void 
ac_fillobj (AC_OBJ obj)
{
  int class_number ;
  if (obj->x->filled)
    return ;

  /*
   * find the object in the database server
   */
  if (find_one (obj->x->db, obj->x->class, obj->x->name) == 0)
    return ;

  /*
   * Turn the class name into a class number.  If we do not know
   * the class name, obviously we will not know the object.
   */
  if (!dictFind (obj->x->db->classDict, obj->x->class, &class_number))
    return  ;

  ac_fillobj_part2 (obj->x->db, &obj, FALSE, class_number, obj->x->name, 0) ;
}

/*
 * ac_fillobj_part2 () assumes that the object is currently the only object
 * in the active keyset.  That is OK because either ac_fillobj () or ac_get ()
 * caused that to be true before calling here.
 *
 * This entry point only exists so we can eliminate the server transaction
 * in ac_fillobj () when you ac_get () an object.
 */
static void
ac_fillobj_part2 (AC_DB db, AC_OBJ *objp, BOOL create, int class_number, char *name, AC_HANDLE handle)
{
  unsigned char *response ;
  int new_class ;
  char *new_name ;
  int response_len ;
  struct parsing *p = 0 ;
  AC_TABLE table ;
  /*
   * Make a table for the object data.  The object is now empty instead
   * of unfilled.  We just need to fill in the data.  If the server
   * response is that there is no data, we do not need to do anything
   * further.
   */
  table = ac_empty_table (10, 0, 0) ;
  /*
   * Show the object.  If the response does not contain any data, then
   * the object is empty.  We already have an empty table, so the object
   * is already empty (as opposed to un-filled) .
   */
  response = ac_command (db, "show -C", &response_len, 0) ;
  if (response)
    {
      p = parse_init (response, response_len, db) ;
      /*
       * must consume the object header even though it contains nothing interesting
       */
      if (parse_object_header (p, &new_class, &new_name ) == 0)
	{
	  if (class_number != new_class)
	    messcrash ("ac_fillobj - server responded with wrong class") ;
	  if (strcasecmp (name, new_name) != 0)
	    messcrash ("ac_fillobj - server responded with wrong object") ;

	  if (create)
	    *objp = cache_create (db, class_number, new_name, handle) ;
	  (*objp)->x->table = table ; 
	  (*objp)->x->filled = TRUE ;
	  (*objp)->x->get_date = ++(db->date) ;
	  /*
	   * parse the object data:
	   */
	  parse_table (p, table, *objp) ;
	}
    }
  else
    {
      if (create)
	{
	  *objp = cache_create (db, class_number, name, handle) ;
	}
      (*objp)->x->table = table ; 
      (*objp)->x->filled = TRUE ;
      (*objp)->x->get_date = ++(db->date) ;
    }

  messfree (response) ;
  messfree (p) ;
  return ;
} /* ac_fillobj_part2 */

/********************************************************************/

/* Private: called by ac_tag_type
 *   Locate the tag in the object table
 */
static BOOL ac_findTag (AC_OBJ obj, char *tag, int *rowp, int *colp)
{
  int tag_number ;
  int i ;
  Array lines ;
  struct tagLine *t ;

  if (! obj->x->filled)
    ac_fillobj (obj) ;

  if (! dictFind (obj->x->db->tag_dict, tag, &tag_number))
    return FALSE ;

  if (! bit (obj->x->hasTag, tag_number))
    return FALSE ;

  lines = obj->x->tagLine ;
  for (i=0 ; i< arrayMax (lines) ; i++)
    {
      t = & array (lines, i, struct tagLine) ;
      if (t->tag_number == tag_number)
	{
	  *rowp = t->row ;
	  *colp = t->col ;
	  return TRUE ;
	}
    }
  return FALSE ;

} /* ac_findTag */


/****************************************************************/

/*
 * Return the number of objects stated in the response to a command
 * that alters the active keyset.
 */
static int active_objects_count (unsigned char *buff)
{
  char cc, *cp = (char *) buff, *cq, *cp0 ;
  int nn ;

  cp0 = cp ;
  while ((cq = strstr (cp, "Active Objects"))) { cp0 = cp ; cp = cq+1 ;} /* find last occurence */
  cp0 = cp ;
  while (cp0 && cp0 > (char *)buff && *cp0 != '/') cp0-- ; cp0++ ;
  while (cp0 && *cp0 == ' ') cp0++ ;
  if (sscanf (cp0, "%d%c", &nn, &cc) == 2 && cc == ' ')
    return nn ;
  return -1 ;      
}

/****************************************************************/

/*
 * load a named keyset into the active keyset
 */

void ac_kget (struct ac_real_keyset *k)
{
  char buff[30] ;
  
  sprintf (buff, "kget _aks%d",  k->id) ;
  k->db->lazy_command (k->db, buff) ;
}

/****************************************************************/

static char* ac_parse_sys (AC_DB db, unsigned char *binaryBuffer, int nn)
{
  int key = 0 ;
  unsigned char kType, *cp = binaryBuffer ;
  int n, ncl = 0, ntags = 0 ;
  unsigned int swap_magic ;
  char *s, buff[50] ;
  
  /*
   * the first 4 bytes are a magic cookie so we can identify what
   * sort of byte swapping is needed
   */
  memcpy (&swap_magic, cp, 4) ; cp+= 4 ; nn -= 4 ;
  switch (swap_magic)
    {
    case 0x12345678:  /* server/client use save byte rules */
      db->swap_needed = FALSE ;
      break ;
    case 0x78563412:  /* server/client disagree */
      db->swap_needed = TRUE ;
      break ;
    default:
      /*
       * No machine we support uses any other byte order than strict big endian
       * or strict little endian.  If we come here, it means there was a bug.
       */
      return "005 bad call to ac_open, server return a bad swap_magic confirmation" ;
    }

  /*
   * skip over the number of tags
   */
  cp+= 3 ;
  nn -= 3 ; 

  while (cp++, nn--)
    switch (*cp)
      {
      case '#': /* end of object */
	db->maxTag = ntags ;	/* remember the number of tags we actually saw */
	/* if (ncl != 256) abort () ; */
	return 0 ;
      case '\n': /* no operation */
      case '>': 
      case '.': /* increment row */ 
	cp++ ; nn-- ;
	kType = *cp++ ; nn-- ;
	switch (kType)
	  {
	  case 'k':
	    ncl++ ;
	    s = (char *)cp ;
	    if (! *s)
	      {
		sprintf (buff, "_sys_%d", ncl) ;
		s = buff ;
	      }
	    dictAdd (db->classDict, s, &key) ;
	    break ;
	  case 'g':
	    ntags++ ;
	    dictAdd (db->tag_dict, (char *)cp, &key) ;
	    break ;
	  }
	/* jump the name itself, zero terminated, followed by \n */
	n = strlen ((char *)cp) ; nn -= n + 1 ; cp += n + 1 ;
	break ;
      default:
	break ;
      }
  messcrash ("missing # terminator in ac_parse_sys") ;

  /* extract the table definition */
  return 0 ;
} /* ac_parse_sys */

/****************************************************************/
/* obtain the class and tag names from the database */
static char* ac_sys (AC_DB db)
{ 
  int nn ;
  unsigned char *binaryBuffer = 0 ;
  char *error = "004 bad call to ac_open, server did not return a system_list" ;

  db->tag_dict = dictHandleCreate (1000, 0) ;
  db->classDict = dictHandleCreate (256, 0) ;
  binaryBuffer = ac_command (db, "system_list", &nn, 0) ;
  if (binaryBuffer)
    {
      error =  ac_parse_sys (db, binaryBuffer, nn) ;
      messfree (binaryBuffer) ;
    }
  return error ;
} /* ac_sys */

/****************************************************************/

/*********************************************************************
**********************************************************************
**********************************************************************
**
** More published library entry points
**
*********************************************************************/


/********************************************************************/

char *ac_name (AC_OBJ obj)
{
  if (obj && obj->x && obj->x->name)
    return obj->x->name ;
  else
    return "(NULL)" ;
}

/********************************************************************/

char * ac_class (AC_OBJ obj)
{
  if (obj)
    return obj->x->class ;
  else
    return " (NULL)" ;
} /* ac_class */


/********************************************************************/
/* PUBLIC
 */
BOOL ac_has_tag	 (AC_OBJ obj, char *tagName)
{
  int row, col ;
  return ac_findTag (obj, tagName, &row, &col) ;
} /* ac_has_tag	 */

/********************************************************************/
/* PUBLIC 
 *  returns the type of the next cell right of the tag
 */ 
ac_type ac_tag_type (AC_OBJ obj, char *tagName)
{
  int row = 0, col = 0 ;

  if (ac_findTag (obj, tagName, &row, &col))
    return ac_table_type (obj->x->table, row, col + 1) ;
  return ac_type_empty ;
} /* ac_tag_type */


int ac_tag_int ( AC_OBJ obj, char *tag, int deflt)
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_int ( obj->x->table, row, col+1, deflt ) ;
  else
    return deflt ;
}
  

float ac_tag_float ( AC_OBJ obj, char *tag, float deflt)
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_float ( obj->x->table, row, col+1, deflt ) ;
  else
    return deflt ;
}
  

char * ac_tag_text ( AC_OBJ obj, char *tag, char * deflt)
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_text ( obj->x->table, row, col+1, deflt ) ;
  else
    return deflt ;
}
  

mytime_t ac_tag_date ( AC_OBJ obj, char *tag, mytime_t deflt)
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_date ( obj->x->table, row, col+1, deflt ) ;
  else
    return deflt ;
}
  

AC_OBJ ac_tag_obj ( AC_OBJ obj, char *tag, AC_HANDLE handle )
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_obj ( obj->x->table, row, col+1, handle ) ;
  else
    return NULL ;
}

char * ac_tag_printable ( AC_OBJ obj, char *tag, char *dflt )
{
  int row, col ;
  if (ac_findTag (obj, tag, &row, &col))
    return ac_table_printable ( obj->x->table, row, col+1, dflt ) ;
  else
    return dflt ;
}
  

/********************************************************************/
/* PUBLIC
 *  fetch a table from a tag in an object
 *  returns a non-writable subtable pointing into the obj->table
 */
AC_TABLE ac_tag_table (AC_OBJ obj, char *tagName, AC_HANDLE handle)
{
  int row, col ;

  if (tagName == NULL)
    {
    if (! obj->x->filled)
      ac_fillobj (obj) ;
    return ac_copy_table (obj->x->table, 0, 0, obj->x->table->rows, obj->x->table->cols, handle) ;
    }
  if (ac_findTag (obj, tagName, &row, &col))
    return ac_subtable (obj->x->table, row, col, handle) ;
  return 0 ;
} /* ac_tag_table */


/****************************************************************/
/* perform AQL query*/

AC_TABLE ac_aql_table ( AC_DB db, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle)
{
  AC_TABLE tt = 0 ;
  unsigned char *binaryBuffer = 0 ;
  int nn = 0 ;

  if (initial_keyset)
    ac_kget (initial_keyset->x) ;
  if (query && *query)
    {
      char * command = halloc (strlen (query) + 100, 0) ;
      sprintf (command, "aql -C %s %s", initial_keyset ? "-active" : "" , query) ;
      binaryBuffer = ac_command (db, command, &nn, handle) ;
      messfree (command) ;
    }
  if (binaryBuffer)
    {
      struct parsing *p ;
      tt = ac_empty_table (100, 0, handle) ;
      p = parse_init ( binaryBuffer, nn, db) ;
      parse_table (p, tt, 0) ;
      messfree (binaryBuffer) ;
      messfree (p) ;
    }
  return tt ;
} /* ac_aql */

/****************************************************************/
/*
 * perform tablemaker query:
 *	db is the database
 * 	query is the query
 * 	where describes how to interpret the value of query
 *           ac_tablemaker_db => query is name of tablemaker query in database
 *           ac_tablemaker_file => query is a file in the client disk space
 *           ac_tablemaker_server_file => query is file name known by server
 *		Not a particularly secure feature
 *           ac_tablemaker_text => query is the actual text of a tablemaker
 *	        query that you read into your source code.
 *	parameters are parameters to substitute in the query
 */

AC_TABLE ac_tablemaker_table ( AC_DB db, char *query, AC_KEYSET initial_keyset, 
			enum ac_tablemaker_where where, 
			char *params, AC_HANDLE handle)
{
  AC_TABLE tt = 0 ;
  unsigned char *binaryBuffer = 0 ;
  int nn = 0 ;
  char *fa = "" ;

  if (initial_keyset)
    {
      if (initial_keyset->x->db != db)
	messcrash ("initial keyset does not belong to database being queried") ;
      ac_kget (initial_keyset->x) ;
      fa = "-active" ;
    }

  if (!params) params = "" ;
  if (query && *query)
    {
      char * command = halloc (strlen (query) + strlen (params) + 100, 0) ;
      switch (where)
	{
	case ac_tablemaker_db:
	  sprintf (command, "table %s -C -n  %s %s", fa, query, params) ;
	  break ;
	case ac_tablemaker_server_file:
	  sprintf (command, "table %s -C -f  %s %s", fa, query, params) ;
	  break ;
	case ac_tablemaker_file:
	  messcrash ("ac_tablemaker_file mode not supported") ;
	  break ;
	case ac_tablemaker_text:
	  sprintf (command, "table %s -C =%s %s", fa, query, params) ;
	  break ;
	}
      /*
       * it would be more efficient if we could collect the various
       * encore responses and parse each of them into the table.
       */
      if (*command)
	binaryBuffer = ac_command (db, command, &nn, handle) ;
      messfree (command) ;
    }
  if (binaryBuffer)
    {
      if (strncmp ((char *)binaryBuffer, "Sorry, ", 6) == 0)
	{
	tt = NULL ;
	}
      else
	{
        struct parsing *p ;

        p = parse_init (binaryBuffer, nn, db) ;
        tt =  ac_empty_table (20, 0, 0) ;
        parse_table (p, tt, 0) ;
        messfree (p) ;
        }

      messfree (binaryBuffer) ;
    }
  return tt ;
}

/****************************************************************/
static AC_OBJ ac_doget_obj (AC_DB db, char *classe, char *name, int fillhint, AC_HANDLE handle)
{ 
  AC_OBJ obj = 0 ;
  int class_number ;

  /*
   * Turn the class name into a class number.  If we do not know
   * the class name, obviously we will not know the object.
   */
  if (!dictFind (db->classDict, classe, &class_number))
    return NULL ;

  /*
   * See if we already have it.
   */
  obj = cache_find ( db, class_number, name, handle) ;

  if (obj)
    {
      if (fillhint && ! obj->x->filled)
        ac_fillobj (obj) ;
      return obj ;
    }


  /*
   * We do not have it.  Get it from the server.
   */

  if (find_one (db, classe, name) == 0)
    return NULL ;

  /*
   * we still need to create the object
   *
   * If we fill it, we garantee the case of the name
   * Do we need to fill it ?
   */
  if (fillhint)
    ac_fillobj_part2 (db, &obj, TRUE, class_number, name, handle ) ;
  else
    obj = cache_create (db, class_number, name, handle ) ;

  return obj ;
} /* ac_doget_obj */


/*
 * PUBLIC ac_get ()
 *
 * find an object and get it from the server
 * always fill it: this insures that casing of name is correct
 */

AC_OBJ ac_get_obj (AC_DB db, char *classe, char *name, AC_HANDLE handle)
{ return ac_doget_obj  (db, classe, name, TRUE, handle) ; }

/****************************************************************/

/*
 * PUBLIC ac_copy_obj ()
 *
 * make another copy of an object
 */

AC_OBJ ac_copy_obj (AC_OBJ obj, AC_HANDLE handle)
{
  /*
   * a lazy implementation so we can have one when we need it.
   * It would be faster to just allocate the object directly.
   */

  AC_OBJ new_obj = 0 ;
  if (obj)
    new_obj = ac_doget_obj ( obj->x->db, obj->x->class, obj->x->name, 
			 0, handle ) ;

  return new_obj ;
}


/****************************************************************/
/****************************************************************/

/*
 * make an empty keyset structure 
 *
 * This is not the same as an empty keyset.  It just initializes the
 * data structure that we use on our end.
 */
static AC_KEYSET 
empty_keyset_struct (AC_DB db, AC_HANDLE handle)
{
  struct ac_keyset *ersatz ;
  struct ac_real_keyset *ks ;

  ks = (struct ac_real_keyset *) halloc (sizeof (struct ac_real_keyset), 0) ;
  ks->db = db ;
  ks->id = ++ (db->keysetId) ; /* unique within the existence of the db handle */
  ks->max = 0 ;
  ks->refcount = 1 ;

  ersatz = (struct ac_keyset *) halloc (sizeof (struct ac_keyset), handle) ;
  blockSetFinalise (ersatz, ac_finalize_keyset) ;
  ersatz->magic = MAGIC_BASE + MAGIC_AC_KEYSET ;
  ersatz->x = ks ;

  return ersatz ;
}


/* PRIVATE
 * implements the different ..query_keyset
 */
static AC_KEYSET ac_doquery_keyset (AC_DB db, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle)
{  
  unsigned char *r = 0 ;
  char *buff ;
  struct ac_keyset *ks = empty_keyset_struct (db, handle) ;

  /*
   * either load the keyset we want to start the query from, or load
   * an empty keyset so the query does not operate on whatever keyset is
   * left over from some unknown previous operation.
   */
  if (initial_keyset == (void*)1) ;
  else if (initial_keyset)
    ac_kget (initial_keyset->x) ;
  else
    {
      db->lazy_command (db, "kget _aks_no_such_keyset") ;
    }

  /*
   * now make the query and save the results in the keyset
   */
  buff = messalloc (20 + (query ? strlen (query) : 10)) ;
  if (initial_keyset == (void*)1) 
    sprintf (buff, "%s" , query) ;
  else
    sprintf (buff, "query %s" , query) ;
  db->lazy_command (db, buff) ;
  
  sprintf (buff, "kstore _aks%d" ,  ks->x->id) ;
  r = ac_command (db, buff, 0, 0) ;
  ks->x->max = active_objects_count (r) ;
  messfree (r) ;
  messfree (buff) ;
  return (AC_KEYSET) ks ;
} /* ac_query_keyset */


/* PUBLIC
 * performs a query, creates a keyset of the result
 */
AC_KEYSET ac_dbquery_keyset (AC_DB db, char *queryText, AC_HANDLE handle)
{
  return ac_doquery_keyset (db, queryText, 0, handle) ;
}
AC_KEYSET ac_ksquery_keyset (AC_KEYSET initial_keyset, char *queryText, AC_HANDLE handle)
{
  return ac_doquery_keyset (initial_keyset->x->db, queryText, initial_keyset, handle) ;
}
AC_KEYSET ac_objquery_keyset (AC_OBJ aobj, char *queryText,  AC_HANDLE handle)
{
 AC_KEYSET aks1 = ac_new_keyset (aobj->x->db, aobj, handle) , aks2 ;

  if (queryText)
    {
      aks2 = ac_doquery_keyset (aobj->x->db, queryText, aks1, handle) ;
      ac_free (aks1) ;

      return aks2 ;
    }
  else
    return aks1 ;
}

/****************************************************************/
/* PUBLIC
 * makes an empty keyset to use keyset operators on
 */
AC_KEYSET 
ac_new_keyset (AC_DB db, AC_OBJ obj, AC_HANDLE handle)
{  
  struct ac_keyset *aks ;
  char buff[50] ;

  aks = empty_keyset_struct (db, handle) ;
 
  ac_kget (aks->x) ;
  sprintf (buff, "kstore _aks%d" ,  aks->x->id) ;
  db->lazy_command (db, buff) ;
  aks->x->max = 0 ;

  if (obj)
	ac_keyset_add (aks, obj) ;

  return (AC_KEYSET) aks ;
}  /* ac_new_keyset */


/****************************************************************/
/* Private
 *   implements the boolean operation
 */
static int ac_keyset_op (AC_KEYSET aks1, AC_KEYSET aks2, char *op)
{
  unsigned char *r ;
  char buff[50] ;
  AC_HANDLE h = ac_new_handle () ;

  if (aks1->x->db != aks2->x->db)
    messcrash ("keyset operation on two keysets that are not in the same database") ;
  ac_kget (aks1->x) ;
  if (op)
    {
      aks1->x->db->lazy_command (aks1->x->db, "spush") ;
      ac_kget (aks2->x) ;
      aks1->x->db->lazy_command (aks1->x->db, op) ;
      aks1->x->db->lazy_command (aks1->x->db, "spop") ;
    }
  sprintf (buff, "kstore _aks%d" ,  aks1->x->id) ;
  r = ac_command (aks1->x->db, buff, 0, h)  ;
  aks1->x->max = active_objects_count (r) ;

  handleDestroy (h) ;
  return aks1->x->max ;
} /* ac_keyset_op */

/****************************************************************/
/* PUBLIC
 * perform boolean operation ks1 = ks1 OP ks2
 * on a keyset.
 * returns the number of element in the result
 */
int ac_keyset_and (AC_KEYSET ks1, AC_KEYSET ks2)
{
  return ac_keyset_op (ks1, ks2, "sand") ;
}

int ac_keyset_or (AC_KEYSET ks1, AC_KEYSET ks2)
{
  return ac_keyset_op (ks1, ks2, "sor") ;
}

int ac_keyset_xor (AC_KEYSET ks1, AC_KEYSET ks2)
{
  return ac_keyset_op (ks1, ks2, "sxor") ;
}

int ac_keyset_minus (AC_KEYSET ks1, AC_KEYSET ks2)
{
  return ac_keyset_op (ks1, ks2, "sminus") ;
}

/****************************************************************/
/* PUBLIC
 * makes another keyset with identical contents.  Because
 * you don't have the 3 operand instructions, you copy the
 * keyset and then use read-modify-write operations on it.
 */

AC_KEYSET ac_copy_keyset (AC_KEYSET aks, AC_HANDLE handle)
{
  AC_KEYSET aks1 = ac_new_keyset (aks->x->db, NULL, handle) ;

  ac_keyset_or (aks1, aks) ;
  return aks1 ;
} /* ac_keyset_copy */

/****************************************************************/
/* PUBLIC
 *
 * add/remove object in keyset 	
 *
 */

BOOL ac_keyset_add (AC_KEYSET aks, AC_OBJ obj)
{
  unsigned char *response ;
  char buff[50] ;

  ac_kget (aks->x) ;

  aks->x->db->lazy_command (aks->x->db, "spush") ;

  if (find_one (aks->x->db, obj->x->class, obj->x->name) == 0)
    return FALSE ;

  aks->x->db->lazy_command (aks->x->db, "sor") ;
  aks->x->db->lazy_command (aks->x->db, "spop") ;

  sprintf (buff, "kstore _aks%d" ,  aks->x->id) ;
  response = ac_command (aks->x->db, buff, 0, 0) ;
  aks->x->max = active_objects_count (response) ;
  messfree (response) ;

  return TRUE ;
} /* ac_keyset_add */

/****************************/

BOOL ac_keyset_remove (AC_KEYSET aks, AC_OBJ obj)
{
  unsigned char *response ;
  char buff[50] ;

  ac_kget (aks->x) ;

  aks->x->db->lazy_command (aks->x->db, "spush") ;

  if (find_one (aks->x->db, obj->x->class, obj->x->name) == 0)
    return FALSE ;

  aks->x->db->lazy_command (aks->x->db, "sminus") ;

  aks->x->db->lazy_command (aks->x->db, "spop") ;

  sprintf (buff, "kstore _aks%d" ,  aks->x->id) ;
  response = ac_command (aks->x->db, buff, 0, 0) ;
  aks->x->max = active_objects_count (response) ;
  messfree (response) ;

  return TRUE ;
} /* ac_keyset_remove */

/****************************************************************/
/* PUBLIC
 * returns a subset strating at x0 of length nx
 * index start at 1 and are ordered alphabetically
 * which is usefull to get slices for display
 */

AC_KEYSET ac_subset_keyset (AC_KEYSET aks, int x0, int nx, AC_HANDLE handle)
{
  unsigned char *r = 0 ;
  AC_KEYSET aks1 = ac_new_keyset (aks->x->db, NULL, handle) ;
  char buff[256] ;

  if (x0<1) x0 = 1 ;
  if (nx > aks->x->max - x0 + 1) nx = aks->x->max - x0 + 1 ;
  sprintf (buff, "subset %d %d", x0, nx) ;
  ac_kget (aks->x) ;
  aks1->x->db->lazy_command (aks1->x->db, buff) ;
  sprintf (buff, "kstore _aks%d" ,  aks1->x->id) ;
  r = ac_command (aks1->x->db, buff, 0, 0) ; 
  aks1->x->max = active_objects_count (r) ; 
  messfree (r) ;
  return aks1 ;
} /* ac_subset_keyset */

/****************************************************************/
/* PUBLIC
 * return the number of keys in a keyset
 */

int ac_keyset_count (AC_KEYSET aks)
{
  return aks->x->max ;
} /* ac_keyset_count */

/****************************************************************/

/****************************************************************
 *
 * Keyset Iterator functions
 *
 ****************************************************************/

/* PUBLIC ac_query_iter ()
 * make a query, dump resulting keyset directly into an iterator
 *	db = database to query
 * 	fillhint = true means load object data during iteration, 
 *		false means use lazy data loading
 *	initial_keyset = keyset to make active before issuing the query
 *	query = query to use
 */
AC_ITER ac_query_iter (AC_DB db, int fillhint, char *query, AC_KEYSET initial_keyset, AC_HANDLE handle)
{
  AC_ITER ret ;
  AC_KEYSET ks ;

  ks = ac_doquery_keyset (db, query, initial_keyset, 0) ;

  ret = ac_keyset_iter ( ks, fillhint, handle ) ;
 
  ac_free (ks) ;

  return ret ;
} /* ac_query_iter */

/****************************************************************/
/* PUBLIC ac_dbquery_iter ()
 * make a query, dump resulting keyset directly into an iterator
 * Short hand 1 of previous function, fillhint = true by default
 *	db = database to query
 *	query = query to use
 */
AC_ITER ac_dbquery_iter (AC_DB db, char *query, AC_HANDLE handle)
{
  return ac_query_iter (db, TRUE, query, 0, handle) ; 
} /* ac_dbquery_iter */

/****************************************************************/
/* PUBLIC ac_ksquery_iter ()
 * make a query, dump resulting keyset directly into an iterator
 * Short hand 2 of previous function, fillhint = true by default
 *	ks = keyset to query
 *	query = query to use
 */
AC_ITER ac_ksquery_iter (AC_KEYSET ks, char *query, AC_HANDLE handle)
{
  if (ks)
    return ac_query_iter (ks->x->db, TRUE, query, ks, handle) ; 
  return 0 ;
} /* ac_ksquery_iter */

/****************************************************************/
/* PUBLIC ac_objquery_iter ()
 * make a query, dump resulting keyset directly into an iterator
 * Short hand 3 of previous function, fillhint = true by default
 *	obj = single object on which we initialise the query
 *	query = query to use
 */
AC_ITER ac_objquery_iter (AC_OBJ obj, char *query, AC_HANDLE handle)
{
  AC_ITER iter = 0 ;
  if (obj)
    {
      AC_KEYSET ks = ac_objquery_keyset (obj, "IS *",  0) ;
      iter = ac_query_iter (obj->x->db, TRUE, query, ks, handle) ;
      ac_free (ks) ;
    }
  return iter ;
} /* ac_objquery_iter */

/****************************************************************/
/* PUBLIC ac_keyset_iter ()
 * create an iterator that loops over a keyset
 *	ks = the keyset to loop over
 * 	fillhint = true means load object data during iteration, 
 *		false means use lazy data loading
 *
 * Set up an iterator to loop over a keyset.  Each call to ac_next_obj
 * will return the next object from the keyset.
 *
 * This function just sets up an empty iterator.  The real work happens
 * in ac_next ()
 */
AC_ITER ac_keyset_iter (AC_KEYSET ks, int fillhint, AC_HANDLE handle) 
{ 
  struct ac_iter *iter = (struct ac_iter *) halloc (sizeof (struct ac_iter), handle) ;
  blockSetFinalise (iter, ac_finalize_iterator) ;

  /*
   * get a reference to the ac_real_keyset
   */
  iter->real_keyset = ks->x ;
  ks->x->refcount++ ;

  /*
   * generally fill in the iterator 
   */
  iter->magic = MAGIC_BASE + MAGIC_AC_ITERATOR ; 
  iter->next = 0 ;
  iter->fillhint = fillhint ;

  iter->ra_next = 0 ;
  iter->ra_max = 0 ;

  iter->user_handle = handle ;

  /*
   * these fields are copied from the keyset for convenience
   */
  iter->db = ks->x->db ;
  iter->id = ks->x->id ;
  iter->max = ks->x->max ;
 
  return (AC_ITER) iter ;
} /* ac_keyset_iter */

/****************************************************************/
/*
 * PRIVATE - ac_iter_show ()
 *
 * re-fill the iterator, using show so we get all the object data
 */

static void
ac_iter_show (AC_ITER it)
{
  unsigned char *response, *response_free ;
  int response_len ;
  struct parsing *p ;
  int class ;
  char *name, buff[80] ;
  int status ;
  int c ;

  /*
   * show the next batch of objects.  we use ac_partial_command instead
   * of ac_command.  The server may choose to send back fewer objects than
   * we ask for.  In that case, ac_command will initiate another transaction 
   * to get the rest of the response.  But we don't really need it.  We
   * continue iterating with the response we have, and when we use up the
   * objects returned, _then_ we will make another transaction anyway.
   * This reduces overhead both in transaction time and memory use in 
   * copying the multiple sections of an encore response into a single
   * buffer.
   *
   */

  sprintf (buff, "show -C -b %d -c %d", it->next, AC_ITER_OBJECT_READAHEAD) ;
  (it->db->ac_partial_command) (it->db, 
			       buff, 
			       &response, 
			       &response_len, 
			       &response_free, 
			       &c) ;

  if (! response)
    messcrash ("null server response") ;
    

  /*
   * Now we have a response containing a bunch of objects.  show sends
   * back a list of objects, which we parse as 
   *		list: element | element list ;
   *		element: object_header table ;
   */
  p = parse_init (response, response_len, it->db) ;
  while ((status = parse_object_header (p, &class, &name)) == 0)
    {
      AC_OBJ o ;
      /*
       * we may already know the object.  If not, create it.
       * Either way, we have a reference to stick on our list.
       */
      o = cache_find (it->db, class, name, it->user_handle) ;
      if (! o)
	o = cache_create (it->db, class, name, it->user_handle ) ;

      it->obj[ it->ra_max ] = o ;
      
      /*
       * If the object already has a table, that means we already
       * have filled the object.  But we have to parse the object
       * data anyway, to skip over it in the buffer.  Here, we
       * discard the data and parse it again.  Unless the data has
       * been updated in the server, the content will be invariant.
       */
      if (o->x->table)
	ac_free (o->x->table) ;

      o->x->table = ac_empty_table (10, 10, 0) ;
      o->x->filled = TRUE ;
      o->x->get_date = ++(p->db->date) ;

      parse_table (p, o->x->table, o) ;

      it->ra_max++ ;
      if (it->ra_max >= AC_ITER_OBJECT_READAHEAD)
	{
	  /*
	   * we can only come here if the server sent back more objects
	   * than we asked for.  That's ok - we can ignore them.
	   */
	  break ;
	}
    }

  messfree (p) ;

  /*
   * remember that response came from ac_partial_command, which is
   * returning a buffer that came out of the RPC library
   */
  free (response_free) ;
}

/******************************************************************/
/*
 * PRIVATE - ac_iter_list ()
 *
 * re-fill the iterator, using list so we get unfilled objects
 */

static void ac_iter_list (AC_ITER it)
{
  unsigned char *response, *response_free ;
  char buff[80] ;
  int response_len ;
  struct parsing *p ;
  int class ;
  char *name ;
  int c ;

  /*
   * fillhint is 0, so we want to just list the objects
   */

  sprintf (buff, "list -C -b %d -c %d", it->next, AC_ITER_OBJECT_READAHEAD) ;
  (it->db->ac_partial_command) (it->db, 
			       buff, 
			       &response, 
			       &response_len, 
			       &response_free, 
			       &c) ;

  if (! response)
    messcrash ("null server response") ;
    
  /*
   * Now we have a response containing a list of objects.
   */
  p = parse_init (response, response_len, it->db) ;

  /*
   * first 5 bytes are not interesting
   */
  (void) parse_getc (p) ;
  (void) parse_getc (p) ;
  (void) parse_getc (p) ;
  (void) parse_getc (p) ;
  (void) parse_getc (p) ;

  /*
   * Now parse the list of object names that came back.
   *
   * These tokens are junk:
   * 	>
   * 	.
   * 	\n
   * 	/.*\n	 (i.e. '/' to '\n')
   *
   * These are what we are looking for:
   * 	K classnumber object_name_string
   * 	k classnumber object_name_string
   */

  while ((c = parse_getc (p)) != -1)
    switch (c)
      {
      case '\n':
      case '.':
      case '>':
	break ;

      case '/':
	while ((c = parse_getc (p)) != '\n')
	  ;
	break ;

      case 'k':
      case 'K':
		
	{
	  AC_OBJ o ;

	  class = parse_getc (p) ;
	  name = parse_getstring (p) ;

	  /*
	   * we may already know the object.  If not, create it.
	   * Either way, we have a reference to stick on our list.
	   */
	  o = cache_find (it->db, class, name, it->user_handle) ;
	  if (! o)
	    o = cache_create (it->db, class, name, it->user_handle ) ;

	  if (c == 'k' && ! o->x->table )
	    {
	      /*
	       * we know the object to be empty, so we can fill the object
	       * now and avoid a server transaction to fill it later.
	       */
	      o->x->table = ac_empty_table (0, 0, 0) ;
 	      o->x->filled = TRUE ;
	      o->x->get_date = ++(it->db->date) ;
	    }

	  it->obj[ it->ra_max ] = o ;
      
	  it->ra_max++ ;
	  if (it->ra_max >= AC_ITER_OBJECT_READAHEAD)
	    {
	      /*
	       * we can only come here if the server sent back more objects
	       * than we asked for.  That's ok - we can ignore them.
	       */
	      break ;
	    }
	}
	break ;
      }

  messfree (p) ;

  /*
   * remember that response came from ac_partial_command, which is
   * returning a buffer that came out of the RPC library
   */
  free (response_free) ;
}

/******************************************************************/
/* PUBLIC ac_next_obj ()
 *
 * Return the next object in an iterator.
 *
 * The iterator contains a read-ahead buffer AC_ITER_OBJECT_READAHEAD
 * objects long.  When we fill the read-ahead, we get references
 * to a bunch of objects.  As we return each object, the
 * reference becomes the property of the application.  We still
 * own the reference for objects that we have not returned, so if
 * we destroy the iterator early, we have to ac_free the un-used
 * objects.
 * 
 */

AC_OBJ ac_next_obj (AC_ITER it)
{

  /*
   * Return NULL if no more objects
   */
  if (it->next > it->max)
    return NULL ;

  /*
   * if the read-ahead buffer is empty, fill it
   */
  if (it->ra_next >= it->ra_max) 
    {

      it->ra_next = 0 ;
      it->ra_max = 0 ;

      /*
       * load the keyset of the iterator.  next we will either
       * "list" or "show" from the active keyset.
       */
      ac_kget (it->real_keyset) ;


      if (it->fillhint)
	ac_iter_show (it) ;
      else
	ac_iter_list (it) ;
    }

  /*
   * return one object from the read-ahead if we have one
   */
  if (it->ra_next < it->ra_max)
    {
      AC_OBJ o ;
      o = it->obj[it->ra_next] ;
      it->obj[it->ra_next] = 0 ;	/* not really necessary */
      it->ra_next++ ;
      it->next++ ;
      return o ;
    }

  /*
   * if we get here, we tried to fill the read-ahead and it was still
   * empty.  We are at the end.
   */
  return NULL ;

} /* ac_next */

/****************************************************************/

/* PUBLIC ac_iter_rewind ()
 *
 * Set the iterator back to the beginning.  This includes freeing
 * any objects in the read-ahead.  
 *
 */

BOOL ac_iter_rewind (AC_ITER it) 
{
  int x ;
  for (x = it->ra_next ; x < it->ra_max ; x++)
    ac_free (it->obj[it->ra_next]) ;
  it->ra_next = 0 ;
  it->ra_max = 0 ;
  it->next = 0 ;
  return TRUE ;
} /* ac_rewind */

/****************************************************************/

/*
 * PUBLIC ac_read_keyset
 *
 * read/write a keyset in non-volatile storage.  This is here so we
 * can preserve keysets from one invocation of a cgi to the next.
 *
 * In the current client implementation, name is a file name on the 
 * server's disk.  The cgi programs take advantage of that by also
 * having an external program clean up old keysets.
 */

AC_KEYSET ac_read_keyset (AC_DB db, char *name, AC_HANDLE handle)
{
  AC_HANDLE h = ac_new_handle () ;
  unsigned char *r ;
  struct ac_keyset *aks ; 
  int max ;
  char *command = messalloc (60 + strlen (name)) ;

  sprintf (command, "keyset-read %s" , name) ;
  r = ac_command (db,  command, 0, NULL) ;

  max = active_objects_count (r) ;

  messfree (r) ;

  if (max == -1)
    return NULL ;

  aks = empty_keyset_struct (db, handle) ;

  aks->x->max = max ;

  sprintf (command, "kstore _aks%d" ,  aks->x->id) ;
  ac_command (db, command, 0, h)  ;

  messfree (command) ;
  handleDestroy (h) ;
  return (AC_KEYSET) aks ;
} /* ac_read_keyset */

/******************************************************************/
/*
 * PUBLIC ac_keyset_write
 */

BOOL ac_keyset_write (AC_KEYSET ks, char *name)
{
  char *command = messalloc (strlen(name)+100) ; 
  unsigned char *r ;

  if (!ks) return FALSE ;
  ac_kget (ks->x) ;

  /*
   * Strictly speaking, we could delay this command, but because it writes
   * non-volatile data to disk I want it to happen immediately.
   */
  sprintf (command, "list -a -f %s", name) ;
  r = ac_command (ks->x->db, command, NULL, NULL) ;
  messfree (r) ;
  messfree (command) ;

  return TRUE ;
} /* ac_write_keyset */

/****************************************************************/
/****************************************************************/
/****************************************************************/

/*
 * PUBLIC ac_keyset_table
 *
 * Create a 1 column table containing all objects in the keyset.
 */

AC_TABLE ac_keyset_table ( AC_KEYSET ks, int min, int max, int fillhint, AC_HANDLE handle )
{
  AC_TABLE tbl ;
  AC_ITER i ;
  int x ;

  if (max == -1)
    max = ks->x->max ;

  tbl = ac_empty_table (max, 1, handle) ;

  /*
   * create an iterator.  we cheat by poking our min value
   * into the iterator at the start.  We can do this because
   * the read-ahead buffer is still empty.
   */
  i = ac_keyset_iter (ks, fillhint, 0) ;
  i->next = min ;


  /*
   * fetch objects from min to max and put them in the table.
   */
  x = min ;
  while (x < max)
    {
      AC_OBJ o ;
      o = ac_next_obj (i) ;
      if (!o)
	break ;	/* never happens */
      ac_table_insert_type (tbl, x, 0, &o, ac_type_obj) ;
      ac_free (o) ;
      x++ ;
    }

  ac_free (i) ;

  return tbl ;
}

/******************************************************************/
/* PUBLIC
 * performs a command, creates a keyset of the result - this is to get
 * keysets from commands that are not part of the library
 */
AC_KEYSET ac_command_keyset (AC_DB db, char *command, AC_KEYSET iks, 
	AC_HANDLE handle)
{  
  unsigned char *r = 0 ;
  char buff[100] ;
  struct ac_keyset *ks = empty_keyset_struct (db, handle) ;

  if (iks)
    ac_kget (iks->x) ;

  db->lazy_command (db, command) ;

  sprintf (buff, "kstore _aks%d" ,  ks->x->id) ;
  r = ac_command (db, buff, 0, 0) ;
  ks->x->max = active_objects_count (r) ;
  messfree (r) ;
  return (AC_KEYSET) ks ;
} /* ac_query_keyset */


unsigned char *ac_a_data (AC_OBJ obj, int *lenp, AC_HANDLE handle)
{
  unsigned char *buf ;
  int len ;

  if (! obj->x->filled )
    ac_fillobj (obj) ;
  len = obj->x->a_data_len ;
  if (lenp)
    *lenp = len ;
  buf = (unsigned char *) halloc (len + 1 , handle) ;
  if (len)
    memcpy (buf, obj->x->a_data, len) ;
  return buf ;
}

/* PUBLIC
   * INCOMPLETE unless you add code in dump.c !!
   * nothing will get exported except for DNA Peptide

* returns a DNA or peptide sequence (according to seq_type).
* The returned string contains only the letters of the
* sequence.  If result_length is not NULL, it is filled in
* with the length of the sequence.
*/

/******************************************************************/
enum ac_sequence_type { ac_sequence_dna, ac_sequence_peptide };

	/*
	* returns a DNA or peptide sequence (according to seq_type).
	* The returned string contains only the letters of the
	* sequence.  If result_length is not NULL, it is filled in
	* with the length of the sequence.
	*
	* messfree() the returned string when you are finished with it.
	*/

static char *ac_sequence (AC_OBJ obj, int *result_length, 
			    enum ac_sequence_type seq_type, 
			    int x1, int x2, AC_HANDLE h)
{
  unsigned char *seq, *in, *out ;
  int n ;
  char command [256] ;

  n = find_one ( obj->x->db, obj->x->class, obj->x->name ) ;
  if (!n)
    return NULL ;
  switch (seq_type)
    {
    case ac_sequence_dna:
      if (x1 || x2)
	sprintf (command, "dna -x1 %d -x2 %d", x1, x2) ;
      else
	sprintf (command, "dna") ;
      seq = ac_command (obj->x->db, command, NULL, h) ;
      break ;
    case ac_sequence_peptide:
      seq = ac_command (obj->x->db, "peptide", NULL, h) ;
      break ;
    default:
      return NULL ;
    }
  
  out = seq ;
  in = seq ;
  while (isspace (*in))
    in++ ;
  if (*in != '>')
    goto bad_resp ;
  
  in = (unsigned char *)strchr ((char *)in, '\n') ;
  if (!in)
    goto bad_resp ;
  
  in++ ;
  
  while (*in)
    {
      if (*in == '/')
	break ;
      if (isspace (*in))
	{
	  in++ ;
	  continue ;
	}
      if (isalpha (*in))
	{
	  *out++ = *in++ ;
	  continue ;
	}
      goto bad_resp ;
    }
  
  *out = 0 ;
  
  if (result_length)
    * result_length = strlen ((char *)seq) ;
  
  return (char *)seq ;
  
 bad_resp:
  messerror ("No sequence available for %s\n", ac_name(obj)) ;
  return NULL ;
}

/******************************************************************/
/* PUBLIC */

char *ac_obj_peptide (AC_OBJ obj, AC_HANDLE h)
{
  int result_length = 0 ;
  return ac_sequence (obj, &result_length, ac_sequence_peptide, 0, 0, h) ;
}

char *ac_obj_dna (AC_OBJ obj, AC_HANDLE h)
{
  int result_length = 0 ;
  return ac_sequence (obj, &result_length, ac_sequence_dna, 0, 0, h) ;
}

char *ac_zone_dna (AC_OBJ obj, int x1, int x2, AC_HANDLE h)
{
  int result_length = 0 ;
  return ac_sequence (obj, &result_length, ac_sequence_dna, x1, x2, h) ;
}

char *ac_longtext (AC_OBJ obj, AC_HANDLE h)
{
  int n = find_one ( obj->x->db, obj->x->class, obj->x->name ) ;
  char* lt ;
  char *cp, *cq ;
  
  if (!n)
    return NULL ;
 
  lt = (char *) ac_command (obj->x->db, "show", NULL, h) ;
  if ((cp = strstr (lt, "***LongTextEnd***")))
    *cp = 0 ;
  else
    return lt ;
   cp = strstr (lt, "LongText") ;
  while (*cp && *cp != '\n') cp++ ; /* jump tiltle line */
  if (strlen (cp))
    cq = strnew (cp, h) ;
  else
    cq = 0 ;
  messfree (lt) ;
  return cq ;
}

/******************************************************************/
/* PUBLIC */

BOOL ac_parse ( AC_DB db, char *text, char **error_messages,  
		AC_KEYSET *nks, AC_HANDLE h )
{
  char *s, *s1 ;
  unsigned char *s2 ;
  int len ;
  AC_KEYSET ks = 0 ;
  char buff[100] ;
  BOOL success = TRUE ;
  
  len = strlen (text) + 100 ;
  s = halloc (len, NULL_HANDLE) ;
  strcpy (s, "serverparse\n") ;
  strcat (s, text) ;
  
  /*
   * create the new keyset first, because it destroys the
   * server's idea of the active keyset
   */
  if (nks)
    ks = ac_new_keyset (db, NULL, h) ;
  
  s1 = (char *) ac_command (db, s, NULL, h) ;
  
  if (strstr (s1, "error"))
    success=FALSE ;
  
  if (error_messages)
    *error_messages = s1 ;
  else
    messfree (s1) ;
  
  if (nks)
    {
      sprintf (buff, "kstore _aks%d" ,  ks->x->id) ;
      s2 = ac_command (db, buff, 0, 0) ;
      ks->x->max = active_objects_count (s2) ;
      messfree (s2) ;
      *nks = ks ;
    }
  
  messfree (s) ;
  return success ;
}

/******************************************************************/
/******************************************************************/
