/* util.c-- wrappers around memory allocation and other misc. */
/*         utilities                                      */
/* Copyright (C) 1996-1999,2001  Robert Masenten          */
/* This program may be redistributed under the terms of the
   GNU General Public License, version 2; see agility.h for details. */
/*                                                        */
/* This is part of the source for both AGiliTy: the (Mostly) Universal */
/*       AGT Interpreter and for the Magx adventure game compiler.    */

/* This includes wrappers for malloc, realloc, strdup, and free 
     that exit gracefully if we run out of memory. */
/* There are also various utilities:
    concdup:  Creates a new string that is concatation of two others.
    strcasecmp: case insensitive comparison of strings 
    strncasecmp: case insensitive compare of first n characters of strings 
    fixsign16, fixsign32: routines to assemble signed ints out of 
          individual bytes in an endian-free way. */
/* Also buffered file i/o routines and some misc. file utilites. */


#include <assert.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <errno.h>
#include "agility.h"

#ifdef force16
#undef int
#endif

#ifdef PROFILE_SUPPORT
#include <sys/times.h>
#endif

#ifdef MEM_INFO
#include <malloc.h>
#endif

#ifdef force16
#define int short
#endif


long rangefix(long n)
{
  if (n>0) return n;
  return 0;
}

/*-------------------------------------------------------------------*/
/* Sign fixing routines, to build a signed 16- and 32-bit quantities */
/* out of their component bytes.                                     */
/*-------------------------------------------------------------------*/

#ifndef FAST_FIXSIGN
short fixsign16(uchar n1,uchar n2)
{
  rbool sflag;
  short n;
  
  if (n2>0x80) {n2&=0x7F;sflag=1;}
  else sflag=0;

  n=n1+(n2<<8);
  if (sflag) n=n-0x7fff-1;
  return n;
}

long fixsign32(uchar n1,uchar n2,uchar n3,uchar n4)
{
  rbool sflag;
  long n;

  if (n4>0x80) {n4&=0x7F;sflag=1;}
  else sflag=0;

  n=n1+(((long)n2)<<8)+(((long)n3)<<16)+(((long)n4)<<24);
  if (sflag) n=n-0x7fffffffL-1L;
  return n;
}
#endif



/*----------------------------------------------------------------------*/
/* rprintf(), uses writestr for output           */
/*  This function is used mainly for diagnostic information */
/* There should be no newlines in the format string or in any of the */
/*  arguments as those could confuse writestr, except for the last */
/*  character in the string which can be a newline. */
/*----------------------------------------------------------------------*/

void rprintf(const char *fmt, ...)
{
  int i;
  char s[100];
  va_list args;

  va_start(args,fmt);
  vsprintf(s,fmt,args);
  va_end(args);
  i=strlen(s)-1;
  if (i>=0 && s[i]=='\n') {
    s[i]=0;
    writeln(s);
  } 
  else writestr(s);
}


/*----------------------------------------------------------------------*/
/* Memory allocation wrappers: All memory allocation should run through */
/* these routines, which trap error conditions and do accounting to     */
/* help track down memory leaks.                                        */
/*----------------------------------------------------------------------*/

rbool rm_trap=1;
 
long get_rm_size(void)
/* Return the amount of space being used by dynamically allocated things */
{
#ifdef MEM_INFO
  struct mstats memdata;

  memdata=mstats();
  return memdata.bytes_used;
#endif
  return 0;
}

long get_rm_freesize(void)
/* Return estimate of amount of space left */
{
#ifdef MEM_INFO
  struct mstats memdata;

  memdata=mstats();
  return memdata.bytes_free;
#endif
  return 0;
}


void *rmalloc(long size)
{
  void *p;

  if (size>MAXSTRUC) {
    printf("Memory allocation error: Over-sized structure requested.\n");
    exit(EXIT_FAILURE);
  }
  assert(size>=0);
  if (size==0) return NULL;
  p=malloc((size_t)size);
  if (p==NULL && rm_trap && size>0) 
    {
      printf("Memory allocation error: Out of memory.\n");
      exit(EXIT_FAILURE);
    }
  if (rm_acct) ralloc_cnt++;
  return p;
}

void *rrealloc(void *old,long size)
{
  void *p;
  
  if (size>MAXSTRUC) {
    printf("Memory reallocation error: Oversized structure requested.\n");
    exit(EXIT_FAILURE);
  }
  assert(size>=0);
  if (size==0) {
    r_free(old);
    return NULL;
  }
  if (rm_acct && old==NULL) ralloc_cnt++;
  p=realloc(old,(size_t)size);
  if (p==NULL && rm_trap && size>0)
    {
      printf("Memory reallocation error: Out of memory.\n");
      exit(EXIT_FAILURE);
    }
  return p;
}

char *rstrdup(const char *s)
{
  char *t;
#ifndef HAVE_STRDUP
  int i;
#endif

  if (s==NULL) return NULL;
#ifdef HAVE_STRDUP
  t=strdup(s);
#else
  t=malloc((strlen(s)+1)*sizeof(char));
#endif
  if (t==NULL && rm_trap)
    {
      printf("Memory duplication error: Out of memory.\n");
      exit(EXIT_FAILURE);
    }
  if (rm_acct) ralloc_cnt++;
#ifndef HAVE_STRDUP
  for(i=0;s[i]!=0;i++)
    t[i]=s[i];
  t[i]=0;
#endif
  return t;
}

void r_free(void *p)
{
  int tmp;

  if (p==NULL) return;

  tmp=get_rm_size();              /* Take worst case in all cases */
  if (tmp>rm_size) rm_size=tmp; 
  tmp=get_rm_freesize();
  if (tmp<rm_freesize) rm_freesize=tmp;

  if (rm_acct) rfree_cnt++;
  free(p);
}



/*----------------------------------------------------------------------*/
/* String utilities: These are utilities to manipulate strings.         */
/*----------------------------------------------------------------------*/

/* rnstrncpy copies src to dest, copying at most (max-1) characters.
   Unlike ANSI strncpy, it doesn't fill extra space will nulls and 
   it always puts a terminating null. */
char *rstrncpy(char *dest,const char *src,int max)
{
  int i;
  for(i=0; i<max-1 && src[i];i++)
    dest[i]=src[i];
  dest[i]=0;
  return dest;
}

/* This does a case-insensitive match of the beginning of *pstr to match */
/* <match> must be all upper case */
/* *pstr is updated to point after the match, if it is succesful.
   Otherwise *pstr is left alone. */
rbool match_str(const char **pstr,const char *match)
{
  int i;
  const char *s;

  s=*pstr;
  for(i=0; match[i]!=0 && s[i]!=0;i++)
    if (toupper(s[i])!=match[i]) return 0;
  if (match[i]!=0) return 0;
  *pstr+=i;
  return 1;
}




/* Utility to concacate two strings with a space inserted */

char *concdup(const char *s1,const char *s2)
{
  int len1, len2;
  char *s;

  len1=len2=0;
  if (s1!=NULL) len1=strlen(s1);
  if (s2!=NULL) len2=strlen(s2);

  s=rmalloc(sizeof(char)*(len1+len2+2));
  if (s1!=NULL) 
    memcpy(s,s1,len1);
  memcpy(s+len1," ",1);
  if (s2!=NULL)
    memcpy(s+len1+1,s2,len2);
  s[len1+len2+1]=0;
  return s;
}


/* Misc. C utility functions that may be supported locally.
  If they are, use the local functions since they'll probably be faster
  and more efficiant. */

#ifdef NEED_STR_CMP
int strcasecmp(const char *s1,const char *s2)
/* Compare strings s1 and s2, case insensitive; */
/* If equal, return 0. Otherwise return nonzero. */
{
  int i;

  for(i=0;tolower(s1[i])==tolower(s2[i]) && s1[i]!=0;i++);
  if (tolower(s1[i])==tolower(s2[i])) return 0;
  if (s1[i]==0) return -1;
  if (s2[i]==0) return 1;
  if (tolower(s1[i])<tolower(s2[i])) return -1;
  return 1;
}
#endif /* NEED_STR_CMP */

#ifdef NEED_STRN_CMP
int strncasecmp(const char *s1,const char *s2,size_t n)
/* Compare first n letters of strings s1 and s2, case insensitive; */
/* If equal, return 0. Otherwise return nonzero. */
{
  size_t i;

  for(i=0;i<n && tolower(s1[i])==tolower(s2[i]) && s1[i]!=0;i++);
  if (i==n || tolower(s1[i])==tolower(s2[i])) return 0;
  if (s1[i]==0) return -1;
  if (s2[i]==0) return 1;
  if (tolower(s1[i])<tolower(s2[i])) return -1;
  return 1;
}
#endif /* NEED_STRN_CMP */

/*----------------------------------------------------------------------*/
/* Character utilities: Do character translation                        */
/*----------------------------------------------------------------------*/

void build_trans_ascii(void)
{
  int i;

  for(i=0;i<256;i++)
    trans_ascii[i]= (!fix_ascii_flag || i<0x80) ? i : trans_ibm[i&0x7f];  
  trans_ascii[0xFF]=0xFF; /* Preserve format character */
}


/*----------------------------------------------------------------------*/
/* File utilities: Utilities to manipulate files.                       */
/*----------------------------------------------------------------------*/

void print_error(char *fname, filetype ext, char *err, rbool ferr)
{
  char *estring; /* Hold error string */
  estring=rmalloc(strlen(err)+strlen(fname)+2);
  sprintf(estring,err,fname);
  if (ferr) fatal(estring); else writeln(estring);
  rfree(estring);
}

/* Routine to open files with extensions and handle basic error conditions */

genfile openfile(fc_type fc,filetype ext,char *err,rbool ferr)
/* Opens the file fname+ext, printing out err if something goes wrong.
  (unless err==NULL, in which case nothing will be printed) */
/* err can have one %s paramater in it, which will have the file name
   plugged in to it. */
/* If ferr is true, then on failure the routine will abort with a fatal
   error. */ 
{
  genfile tfile;  /* Actually, this may not be a text file anymore */
  char *errstr;
  
  tfile=readopen(fc,ext,&errstr);
  if (errstr!=NULL && err!=NULL)
    print_error("",ext,err,ferr);
  rfree(errstr);
  return tfile;
}


genfile openbin(fc_type fc,filetype ext,char *err,rbool ferr)
/* Opens the file fname+ext, printing out err if something goes wrong.
  (unless err==NULL, in which case nothing will be printed) */
/* err can have one %s paramater in it, which will have the file name
   plugged in to it. */
/* If ferr is true, then on failure the routine will abort with a fatal
   error. */ 
{
  genfile f;  /* Actually, this may not be a text file anymore */
  char *errstr;
  char *fname;
  
  f=readopen(fc,ext,&errstr);
  if (errstr!=NULL && err!=NULL) {
    fname=formal_name(fc,ext);
    print_error(fname,ext,err,ferr);
    rfree(fname);
  }
  rfree(errstr);
  return f;
}



/* This routine reads in a line from a 'text' file; it's designed to work
   regardless of the EOL conventions of the platform, at least up to a point. 
   It should work with files that have \n, \r, or \r\n termined lines.  */

#define READLN_GRAIN 64 /* Granularity of readln() rrealloc requests 
			    this needs to be at least the size of a tab 
			    character */
#define DOS_EOF 26    /* Ctrl-Z is the DOS end-of-file marker */

char *readln(genfile f, char *buff, int n)
     /* Reads first n characters of line, eliminates any newline,
	and truncates the rest of the line. 'n' does *not* include terminating
	null. */
     /* If we pass it BUFF=NULL,  then it will reallocate buff as needed and 
	pass it back as its return value.  n is ignored in this case */
     /* If it reaches EOF, it will return NULL */
     /* This routine recognizes lines terminated by \n, \r, or \r\n */
{
  int c;
  int i, j, csize;
  int buffsize; /* Current size of buff, if we are allocating it dynamically */

  if (buff==NULL) {
    buff=rrealloc(buff,READLN_GRAIN*sizeof(char));
    buffsize=READLN_GRAIN;
    n=buffsize-1;
  }
  else buffsize=-1;  /* So we know that we are using a fixed-size buffer */

  i=0;
  for(;;) {
    c=textgetc(f);

    if (c=='\n' || c=='\r' || c==EOF || c==DOS_EOF) break;

    csize= (c=='\t') ? 5 : 1;  /* Tabs are translated into five spaces */

    if (i+csize>=n && buffsize>=0) {
      buffsize+=READLN_GRAIN; n=buffsize-1;
      buff=rrealloc(buff,buffsize*sizeof(char));
    }

    if (c==0) c=FORMAT_CODE;
    else if (c!='\t') {if (i<n) buff[i++]=c;}
    else for(j=0;j<5 && i<n;j++) buff[i++]=' ';    

    /* We can't exit the loop if i>n since we still need to discard
       the rest of the line */
  } 

  buff[i]=0; 

  if (c=='\r') { /* Check for \r\n DOS-style newline  */
    char newc;
    newc=textgetc(f);   
    if (newc!='\n') textungetc(f,newc);
        /* Replace the character we just read. */
  } else if (c==DOS_EOF)  /* Ctrl-Z is the DOS EOF marker */
    textungetc(f,c);  /* So it will be the first character we see next time */

  if (i==0 && (c==EOF || c==DOS_EOF)) {/* We've hit the end of the file */
    if (buffsize>=0) rfree(buff);
    return NULL;
  }

  if (buffsize>=0) {  /* Shrink buffer to appropriate size */
    buffsize=i+1;
    buff=rrealloc(buff,buffsize);
  }

  return buff;
}


/*-------------------------------------------------------------------------*/
/* Buffered file Input: Routines to do buffered file I/O for files organized */
/*  into records.  These routines are highly non-reentrant: they use a     */
/*  global buffer and a global file id, so only they can only access one   */
/*  file at a time.                                                        */
/* buffopen() should not be called on a new file until buffclose has been  */
/*  called on the old one.                                                 */
/*-------------------------------------------------------------------------*/

genfile bfile;

static uchar *buffer=NULL;
static long buffsize; /* How big the buffer is */
static long record_size;  /* Size of a record in the file */
static long buff_frame;  /* The file index corrosponding to buffer[0] */
static long buff_fcnt;  /* Number of records that can be held in the buffer */ 
static long real_buff_fcnt;  /* Number of records actually held in buffer */
static long buff_rsize;  /* Minimum amount that must be read. */

static long block_size;  /* Size of the current block 
			    (for non-AGX files, this is just the filesize) */
static long block_offset; /* Offset of current block in file (this should
			   be zero for non-AGX files) */


static void buff_setrecsize(long recsize)
{
  char *errstr;

  record_size=recsize;
  real_buff_fcnt=buff_fcnt=buffsize/record_size;
  buff_frame=0;
    
  /* Note that real_buff_cnt==buff_fcnt in this case because
     the buffer will have already been resized to be <=
     the block size-- so we don't need to worry about the 
     buffer being larger than the data we're reading in. */

  binseek(bfile,block_offset);
  if (!binread(bfile,buffer,record_size,real_buff_fcnt,&errstr))
    fatal(errstr);
}



long buffopen(fc_type fc,filetype ext,long minbuff,char *rectype,long recnum)
     /* Returns record size; print out error and halt on failure  */
     /* (if agx_file, it returns the filesize instead) */
     /* rectype="noun","room", etc.  recnum=number of records expected */
     /* If rectype==NULL, buffopen() will return 0 on failure instead of
	halting */
     /* For AGX files, recsize should be set to minbuff... but
	buffreopen will be called before any major file activity 
	(in particular, recnum should be 1) */
{
  long filesize;
  long recsize;
  char ebuff[200];
  char *errstr;

  assert(buffer==NULL); /* If not, it means these routines have been 
			   called by someone else who isn't done yet */

  bfile=readopen(fc,ext,&errstr);
  if (errstr!=NULL) {
    if (rectype==NULL) 
      { rfree(errstr); return 0;}
    else
      fatal(errstr);
  }

  filesize=binsize(bfile);

  block_size=filesize;
  block_offset=0;
  if (agx_file) block_size=minbuff; /* Just for the beginning */

  if (block_size % recnum !=0) {
      sprintf(ebuff,"Fractional record count in %s file.",rectype);
      agtwarn(ebuff,0);}
  buff_rsize=recsize=block_size/recnum;
  if (buff_rsize>minbuff) buff_rsize=minbuff;

 /* No point in having a buffer bigger than the block size */
  buffsize=BUFF_SIZE;
  if (block_size<buffsize) buffsize=block_size; 

  /* ... but it needs to be big enough: */
  if (buffsize<minbuff) buffsize=minbuff;
  if (buffsize<recsize) buffsize=recsize;

  buffer=rmalloc(buffsize); /* Might want to make this adaptive eventually */
  
  buff_setrecsize(recsize);
  if (!agx_file && DIAG) {
    char *s;    
    s=formal_name(fc,ext);
    rprintf("Reading %s file %s (size:%ld)\n",rectype,s,filesize);
    rfree(s);
    rprintf("  Record size=  Formal:%d    File:%ld",minbuff,recsize);
  }
  if (agx_file) return (long) filesize;
  else return (long) recsize;
}


/* Compute the game signature: a checksum of relevant parts of the file */

static void compute_sig(uchar *buff)
{ 
  long bp;
  for(bp=0;bp<buff_rsize;bp++) 
    game_sig=( game_sig+buff[bp] ) & 0xFFFF;
}

uchar *buffread(long index)
{
  uchar *bptr;
  char *errstr;

  assert(buff_rsize<=record_size);
  if (index>=buff_frame && index<buff_frame+real_buff_fcnt)
    bptr=buffer+(index-buff_frame)*record_size;
  else {
    binseek(bfile,block_offset+index*record_size);
    real_buff_fcnt=block_size/record_size-index;  /* How many records 
						     could we read in? */
    if (real_buff_fcnt>buff_fcnt) 
      real_buff_fcnt=buff_fcnt;  /* Don't overflow buffer */
    if (!binread(bfile,buffer,record_size,real_buff_fcnt,&errstr))
      fatal(errstr);
    buff_frame=index;
    bptr=buffer;
  }
  if (!agx_file) compute_sig(bptr);
  return bptr;
}


void buffclose(void)
{
  readclose(bfile);  
  rfree(buffer);
}


/* This changes the record size and offset settings of the buffered
   file so we can read files that consist of multiple sections with
   different structures */
static void buffreopen(long f_ofs, long file_recsize, long recnum, 
		       long bl_size, char *rectype)
{
  char ebuff[200];
  long recsize;

  /* Compute basic statistics */
  block_offset=f_ofs;   /* Offset of this block */
  block_size=bl_size;   /* Size of the entire block (all records) */
  if (block_size % recnum !=0) {
    /* Check that the number of records divides the block size evenly */
      sprintf(ebuff,"Fractional record count in %s block.",rectype);
      agtwarn(ebuff,0);}
  buff_rsize=recsize=block_size/recnum;
  if (buff_rsize>file_recsize) buff_rsize=file_recsize;
  /* recsize is the size of each record in the file.
     buff_rsize is the internal size of each record (the part
     we actually look at, which may be smaller than recsize) */

  /* No point in having a buffer bigger than the block size */
  buffsize=BUFF_SIZE;
  if (block_size<buffsize) buffsize=block_size; 

  /* The buffer needs to be at least as big as one block, so
     we have space to both read it in and so we can look at the
     block without having to worry about how big it really is */
  if (buffsize<file_recsize) buffsize=file_recsize;
  if (buffsize<recsize) buffsize=recsize;

  rfree(buffer);
  buffer=rmalloc(buffsize); /* Resize the buffer */

  buff_setrecsize(recsize); /* Set up remaining stats */
}


/*-------------------------------------------------------------------------*/
/* Buffered file output: Routines to buffer output  for files organized    */
/*  into records.  These routines are highly non-reentrant: they use a     */
/*  global buffer and a global file id, so only they can only access one   */
/*  file at a time.                                                        */
/* This routine uses the same buffer and data structures as the reading    */
/*  routines above, so  both sets of routines should not be used           */
/*  concurrently                                                           */
/*-------------------------------------------------------------------------*/

/* #define DEBUG_SEEK*/  /* Debug seek beyond EOF problem */

static long bw_first,bw_last;  /* First and last record in buffer written to. 
				 This is relative to the beginning of the 
				 buffer bw_last points just beyond the last 
				 one written to */
#ifdef DEBUG_SEEK
static long bw_fileleng;  /* Current file length */
#endif /* DEBUG_SEEK */
file_id_type bw_fileid;

/* Unlike is reading counterpart, this doesn't actually allocate
   a buffer; that's done by bw_setblock() which should be called before
   any I/O */
void bw_open(fc_type fc,filetype ext)
{
  char *errstr;

  assert(buffer==NULL);

  bfile=writeopen(fc,ext,&bw_fileid,&errstr);
  if (errstr!=NULL) fatal(errstr);
  bw_last=0;
  buffsize=0;
  buffer=NULL;
#ifdef DEBUG_SEEK
  bw_fileleng=0;
#endif
}

static void bw_seek(long offset)
{
#ifdef DEBUG_SEEK
  assert(offset<=bw_fileleng);
#endif
  binseek(bfile,offset);
}


static void bw_flush(void)
{
  if (bw_first==bw_last) return; /* Nothing to do */
  bw_first+=buff_frame; 
  bw_last+=buff_frame;
  bw_seek(block_offset+bw_first*record_size);
  binwrite(bfile,buffer,record_size,bw_last-bw_first,1);
#ifdef DEBUG_SEEK
  if (block_offset+bw_last*record_size>bw_fileleng)
    bw_fileleng=block_offset+bw_last*record_size;
#endif
  bw_first=bw_last=0;
}



static void bw_setblock(long fofs, long recnum, long rsize)
     /* Set parameters for current block */
{
  /* First, flush old block if neccessary */
  if (buffer!=NULL) {
      bw_flush();
      rfree(buffer);
  }
  block_size=rsize*recnum;
  block_offset=fofs;
  record_size=rsize;
  buff_frame=0;bw_first=bw_last=0;
  buffsize=BUFF_SIZE;
  if (buffsize>block_size) buffsize=block_size;
  if (buffsize<rsize) buffsize=rsize;
  buff_fcnt=buffsize/rsize;
  buffsize=buff_fcnt*rsize;
  buffer=rmalloc(buffsize);
}

/* This routine returns a buffer of the current recsize and with
 the specified index into the file */
/* The buffer will be written to disk after the next call to
   bw_getbuff() or bw_closebuff() */
static uchar *bw_getbuff(long index)
{
  index-=buff_frame;
  if (index<bw_first || index>bw_last || index>=buff_fcnt) {
    bw_flush();
    bw_first=bw_last=0;
    buff_frame=buff_frame+index;
    index=0;
  }
  if (index==bw_last) bw_last++;
  return buffer+record_size*index;
}


/* This flushes all buffers to disk and closes all files */
void bw_close(void)
{
  bw_flush();
  rfree(buffer);
  writeclose(bfile,bw_fileid);
}

void bw_abort(void)
{
  binremove(bfile,bw_fileid);
}


/*-------------------------------------------------------------------------*/
/* Block reading and writing code and support for internal buffers         */
/*-------------------------------------------------------------------------*/


/* If the internal buffer is not NULL, it is used instead of a file */
/* (This is used by RESTART, etc. to save state to memory rather than
   to a file) */
static uchar *int_buff=NULL;
static long ibuff_ofs, ibuff_rsize;

void set_internal_buffer(void *buff)
{
  int_buff=(uchar*)buff;
}

static void set_ibuff(long offset, long rsize)
{
  ibuff_ofs=offset;
  record_size=ibuff_rsize=rsize;
}

static uchar *get_ibuff(long index)
{
  return int_buff+ibuff_ofs+index*ibuff_rsize;
}

/* This does a block write to the currently buffered file.
   At the moment this itself does no buffering at all; it's intended
   for high speed reading of blocks of chars for which we've already
   allocated the space. */
static void buff_blockread(void *buff,long size,long offset)
{
  char *errstr;

  if (int_buff!=NULL)
    memcpy((char*)buff,int_buff+offset,size);
  else {
    binseek(bfile,offset);
    if (!binread(bfile,buff,size,1,&errstr)) fatal(errstr);
  }
}


/* This writes buff to disk. */
static void bw_blockwrite(char *buff,long size,long offset)
{
  if (int_buff!=NULL)
    memcpy(int_buff+offset,(char*)buff,size);
  else {
    bw_flush();
    bw_seek(offset);
    binwrite(bfile,buff,size,1,1);
#ifdef DEBUG_SEEK
    if (offset+size>bw_fileleng) bw_fileleng=offset+size;
#endif
  }
}


/*-------------------------------------------------------------------------*/
/* Platform-independent record-based file I/O: Routines to read and write  */
/*   files according to the file_info data structures.                     */
/* These routines use the buffered I/O routines above                      */
/*-------------------------------------------------------------------------*/

 /* Length of file datatypes */
const size_t ft_leng[FT_COUNT]={0,2,2, /* END, int16, and uint16 */
				4,4,   /* int32 and uint32 */
				1,2,0,  /* byte, version, rbool */
				8,4,   /* descptr, ss_ptr */
				2,26,   /* slist, path[13] */
				4,4,  /* cmdptr, dictptr */  
				81,  /* tline */
				1,1};  /* char, cfg */


long compute_recsize(file_info *recinfo)
{
  long cnt, bcnt;

  cnt=0;
  for(;recinfo->ftype!=FT_END;recinfo++)
    if (recinfo->ftype==FT_BOOL) {
      for(bcnt=0;recinfo->ftype==FT_BOOL;recinfo++,bcnt++);
      recinfo--;
      cnt+=(bcnt+7)/8;   /* +7 is to round up */
    } else 
      cnt+=ft_leng[recinfo->ftype];
  return cnt;
}

static const int agx_version[]={0,0000,1800,2000,3200,3500,8200,8300,
		   5000,5050,5070,10000,10050,15000,15500,16000,20000};

static int agx_decode_version(int vercode)
{
  if (vercode&1) /* Large/Soggy */
    if (vercode==3201) ver=4;
    else ver=2;
  else 
    if (vercode<10000) ver=1;
    else ver=3;
  switch(vercode&(~1)) 
    {
    case 0000: return AGT10;
    case 1800: return AGT118;
    case 1900: return AGT12;
    case 2000: return AGT12;
    case 3200: return AGTCOS;
    case 3500: return AGT135;
    case 5000: return AGT15;
    case 5050: return AGT15F;
    case 5070: return AGT16;
    case 8200: return AGT182;
    case 8300: return AGT183;
    case 8350: return AGT183;
    case 10000:return AGTME10;
    case 10050:return AGTME10A;
    case 15000:return AGTME15;
    case 15500:return AGTME155;
    case 16000:return AGTME16;
    case 20000:return AGX00;
    default:agtwarn("Unrecognize AGT version",0);
      return 0;
    }
}

/* The following reads a section of a file into variables, doing
   the neccessary conversions. It is the foundation of all the generic
   file reading code */

#define p(t)  ((t*)(rec_desc->ptr))
#define fixu16(n1,n2)  ( ((long)(n1))|( ((long)(n2))<<8 ))

/* This is as large as the largest data structure we could run into */
static const uchar zero_block[81]={0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0,0,0,0,0,0,0,0,
				   0};

static void read_filerec(const file_info *rec_desc, const uchar *filedata)
{
  uchar mask;
  rbool past_eob; /* Are we past the end of block? */
  const uchar *filebase;

  mask=1;
  past_eob=0;
  filebase=filedata;
  for(;rec_desc->ftype!=FT_END;rec_desc++) {
    if (mask!=1 && rec_desc->ftype!=FT_BOOL) { /* Just finished rboolean */
      mask=1;
      filedata+=1;
    }
    if (filebase==NULL || (filedata-filebase)>=record_size ) {
      /* We're past the end of the block; read in zeros for the rest
	 of entries. */
      past_eob=1;
      filedata=zero_block;
      filebase=NULL;
    }
    switch(rec_desc->ftype) 
      {
      case FT_INT16:
	if (rec_desc->dtype==DT_LONG)
	  *p(long)=fixsign16(filedata[0],filedata[1]);
	else 
	  *p(integer)=fixsign16(filedata[0],filedata[1]);
	break;
      case FT_UINT16: 
	*p(long)=fixu16(filedata[0],filedata[1]);
	break;              
      case FT_CMDPTR: /* cmd ptr */
      case FT_INT32: 
	*p(long)=fixsign32(filedata[0],filedata[1],
			   filedata[2],filedata[3]);
	break;
      case FT_UINT32:
	if (filedata[3]&0x80) 
	  agtwarn("File value out of range",0);
	*p(long)=fixsign32(filedata[0],filedata[1],
			   filedata[2],filedata[3]&0x7F);
	break;
      case FT_BYTE: *p(uchar)=filedata[0];break;
      case FT_CHAR: 
	*p(uchar)=trans_ascii[filedata[0]^'r'];
	break;
      case FT_VERSION:
	*p(int)=agx_decode_version(fixu16(filedata[0],filedata[1]));      
	break;
      case FT_CFG:
	if (filedata[0]!=2 && !past_eob)
	  *p(rbool)=filedata[0];
	break;
      case FT_BOOL: 
	*p(rbool)=( (filedata[0]&mask)!=0);
	if (mask==0x80) {
	  filedata++; mask=1;
	} else
	  mask<<=1;
	break;
      case FT_DESCPTR:
	if (skip_descr) break; 
	p(descr_ptr)->start=fixsign32(filedata[0],filedata[1],
				      filedata[2],filedata[3]);
	p(descr_ptr)->size=fixsign32(filedata[4],filedata[5],
				      filedata[6],filedata[7]);
	break;
      case FT_STR:  /* ss_string ptr */
	*p(char*)=static_str+fixsign32(filedata[0],filedata[1],
				       filedata[2],filedata[3]);
	break;
      case FT_SLIST: 
	*p(slist)=fixsign16(filedata[0],filedata[1]);
	break;
      case FT_PATHARRAY: /* integer array[13] */ 
	{ 
	  int i;
	  for(i=0;i<13;i++)
	    p(integer)[i]=fixsign16(filedata[2*i],filedata[2*i+1]);
	  break;
	}
      case FT_TLINE: { /* string of length at most 80 characters +null */
	uchar *s;
	int i;
	s=(uchar*)*p(tline);
	for(i=0; i<80; i++) 
	  s[i]=trans_ascii[filedata[i]^'r'];
	s[80]=0;
	break;
      }
      case FT_DICTPTR: /* ptr into dictstr */
	*p(char*)=dictstr+fixsign32(filedata[0],filedata[1],
				    filedata[2],filedata[3]);
	break;
      default:fatal("Unreconized field type");
    }
    filedata+=ft_leng[rec_desc->ftype];
  }
}


#define v(t) (*(t*)(rec_desc->ptr))
/* Here is the corresponding routien for _writing_ to files */
/* This copies the contents of a record into a buffer */

static void write_filerec(const file_info *rec_desc, uchar *filedata)
{
  uchar mask;

  mask=1;
  for(;rec_desc->ftype!=FT_END;rec_desc++) {
    if (mask!=1 && rec_desc->ftype!=FT_BOOL) { /* Just finished rboolean */
      mask=1;
      filedata+=1;
    }
    switch(rec_desc->ftype) 
      {
      case FT_INT16:
	if (rec_desc->dtype==DT_LONG) {
	  filedata[0]=v(long)&0xFF;
	  filedata[1]=(v(long)>>8)&0xFF;
	} else {
	  filedata[0]=v(integer)&0xFF;
	  filedata[1]=(v(integer)>>8)&0xFF;
	}
	break;
      case FT_UINT16: 
	filedata[0]=v(long)&0xFF;
	filedata[1]=(v(long)>>8)&0xFF;
	break;      
      case FT_CMDPTR: /* cmd ptr */        
      case FT_INT32:
      case FT_UINT32:
	filedata[0]=v(long)&0xFF;
	filedata[1]=(v(long)>>8)&0xFF;
	filedata[2]=(v(long)>>16)&0xFF;
	filedata[3]=(v(long)>>24)&0xFF;
	break;
      case FT_BYTE: filedata[0]=v(uchar);break;
      case FT_CFG: filedata[0]=v(uchar);break;
      case FT_CHAR: filedata[0]=v(uchar)^'r';break;
      case FT_VERSION:
	{ 
	  int tver;
	  tver=agx_version[v(int)];
	  if (ver==2 || ver==4) tver+=1;
	  filedata[0]=tver&0xFF;
	  filedata[1]=(tver>>8)&0xFF;
	  break;
	}
      case FT_BOOL: 
	if (mask==1) filedata[0]=0;
	filedata[0]|=v(rbool)?mask:0;
	if (mask==0x80) {
	  filedata++; mask=1;
	} else
	  mask<<=1;
	break;
      case FT_DESCPTR:
	{
	  long i,n1,n2;
	  n1=p(descr_ptr)->start;
	  n2=p(descr_ptr)->size;
	  for(i=0;i<4;i++) {
	    filedata[i]=n1&0xFF;
	    filedata[i+4]=n2&0xFF;
	    n1>>=8;n2>>=8;
	  }
	}
	break;
      case FT_STR:  /* ss_string ptr */
	{ 
	  long delta;
	  delta=v(char*)-static_str;
	  filedata[0]=delta&0xFF;
	  filedata[1]=(delta>>8)&0xFF;
	  filedata[2]=(delta>>16)&0xFF;
	  filedata[3]=(delta>>24)&0xFF;
	  break;
	}
      case FT_SLIST:
	filedata[0]=v(slist)&0xFF;
	filedata[1]=(v(slist)>>8)&0xFF;
	break;
      case FT_PATHARRAY: /* integer array[13] */ 
	{ 
	  int i;
	  for(i=0;i<13;i++) {
	    filedata[2*i]=*(p(integer)+i)&0xFF;
	    filedata[2*i+1]=(*(p(integer)+i)>>8)&0xFF;
	  }
	  break;
	}
      case FT_TLINE: { /* string of length at most 80 characters +null */
	uchar *s;
	int i;
	s=(uchar*)v(tline);
	for(i=0; i<80; i++) 
	  filedata[i]=s[i]^'r';
	filedata[80]=0;
	break;
      }
      case FT_DICTPTR: /* ptr into dictstr */
	{
	  long delta;
	  delta=v(char*)-dictstr;
	  filedata[0]=delta&0xFF;
	  filedata[1]=(delta>>8)&0xFF;
	  filedata[2]=(delta>>16)&0xFF;
	  filedata[3]=(delta>>24)&0xFF;
	  break;
	}
      default:fatal("Unreconized field type");
      }
    filedata+=ft_leng[rec_desc->ftype];
  }
}

#undef v
#undef p




/* This reads in a structure array */
/* base=the beginning of the array. If NULL, this is malloc'd and returned 
   eltsize = the size of each structure
   numelts = the number of elements in the array
   field_info = the arrangement of fields within the strucutre
   rectype = string to print out for error messages
   file_offset = the offset of the beginning of the array into the file 
   */
void *read_recarray(void *base, long eltsize, long numelts, 
		   file_info *field_info, char *rectype,
		   long file_offset, long file_blocksize) 
{
  long i;
  file_info *curr;
  uchar *file_data;

  if (numelts==0) return NULL;

  if (int_buff)
    set_ibuff(file_offset,compute_recsize(field_info));
  else
    buffreopen(file_offset, compute_recsize(field_info), numelts, 
	       file_blocksize, rectype);

  if (base==NULL)
    base=rmalloc(eltsize*numelts);

  for(curr=field_info;curr->ftype!=FT_END;curr++)
    if (curr->dtype!=DT_DESCPTR && curr->dtype!=DT_CMDPTR)
      curr->ptr=( (char*)base + curr->offset);

  for(i=0;i<numelts;i++) {
    if (!int_buff)
      file_data=buffread(i);
    else
      file_data=get_ibuff(i);
    read_filerec(field_info,file_data);
    for(curr=field_info;curr->ftype!=FT_END;curr++) 
      if (curr->dtype==DT_DESCPTR)
	curr->ptr=(char*)(curr->ptr)+sizeof(descr_ptr);
      else if (curr->dtype==DT_CMDPTR)
	curr->ptr=(char*)(curr->ptr)+sizeof(long);
      else 
	curr->ptr=(char*)(curr->ptr)+eltsize;  
  }

  return base;
}


/* A NULL value means to write junk; we're just producing 
   a placeholder for systems that can't seek beyond the end-of-file */

long write_recarray(void *base, long eltsize, long numelts, 
		   file_info *field_info, long file_offset) 
{
  long i;
  file_info *curr;
  uchar *file_data;

  if (numelts==0) return 0;

  if (int_buff)
    set_ibuff(file_offset,compute_recsize(field_info));
  else
    bw_setblock(file_offset, numelts, compute_recsize(field_info));

  if (base!=NULL)
    for(curr=field_info;curr->ftype!=FT_END;curr++)
      if (curr->dtype!=DT_DESCPTR && curr->dtype!=DT_CMDPTR)
	curr->ptr=( (char*)base + curr->offset);

  for(i=0;i<numelts;i++) {
    if (int_buff)
      file_data=get_ibuff(i);
    else
      file_data=bw_getbuff(i);
    if (base!=NULL) {
      write_filerec(field_info,file_data);
      for(curr=field_info;curr->ftype!=FT_END;curr++) 
	if (curr->dtype==DT_DESCPTR)
	  curr->ptr=(char*)(curr->ptr)+sizeof(descr_ptr);
	else if (curr->dtype==DT_CMDPTR)
	  curr->ptr=(char*)(curr->ptr)+sizeof(long);
	else 
	  curr->ptr=(char*)(curr->ptr)+eltsize;  
    }
  }
  return compute_recsize(field_info)*numelts;
}


void read_globalrec(file_info *global_info, char *rectype,
		    long file_offset, long file_blocksize) 
{
  uchar *file_data;

  if (int_buff) {
    set_ibuff(file_offset,compute_recsize(global_info));
    file_data=get_ibuff(0);
  } else {
    buffreopen(file_offset, compute_recsize(global_info), 1, file_blocksize,
	       rectype);
    file_data=buffread(0);
  }
  read_filerec(global_info,file_data);
}


long write_globalrec(file_info *global_info, long file_offset) 
{
  uchar *file_data;

  if (int_buff) {
    set_ibuff(file_offset,compute_recsize(global_info));
    file_data=get_ibuff(0);
  } else {
    bw_setblock(file_offset, 1, compute_recsize(global_info));
    file_data=bw_getbuff(0);
  }
  write_filerec(global_info,file_data);
  return compute_recsize(global_info);
}



static file_info fi_temp[]={
  {0,DT_DEFAULT,NULL,0},
  endrec
};

/* This routine reads in an array of simple data */

void *read_recblock(void *base,int ftype,long numrec, 
		    long offset, long bl_size)
{
  int dsize;

  switch(ftype) 
    {
    case FT_CHAR: 
    case FT_BYTE: 
      if (base==NULL) base=rmalloc(numrec*sizeof(char));
      buff_blockread(base,numrec,offset);
      if (ftype==FT_CHAR) {
	long i;
	for(i=0;i<numrec;i++) 
	  ((uchar*)base)[i]=trans_ascii[ ((uchar*)base)[i]^'r' ];
      }
      return base;
    case FT_SLIST: dsize=sizeof(slist);break;
    case FT_INT16: dsize=sizeof(integer);break;
    case FT_UINT16:
    case FT_INT32: dsize=sizeof(long); break;
    case FT_STR:
    case FT_DICTPTR: dsize=sizeof(char*);break;
    default: 
      fatal("Invalid argument to read_recblock.");
      dsize=0; /* Silence compiler warnings; this will never actually
		  be reached. */
    } 

  fi_temp[0].ftype=ftype;
  return read_recarray(base, dsize, numrec, fi_temp, "", offset, bl_size);
}


long write_recblock(void *base,int ftype,long numrec, long offset)
{
  int dsize;

  if (numrec==0) return 0;
  switch(ftype) 
    {
    case FT_CHAR: {
      int i;
      for(i=0;i<numrec;i++) 
	((uchar*)base)[i]=((uchar*)base)[i]^'r';
    }
    /* Fall through.... */
    case FT_BYTE:
      bw_blockwrite(base,numrec,offset);
      return numrec;
    case FT_SLIST: dsize=sizeof(slist);break;
    case FT_INT16: dsize=sizeof(integer);break;
    case FT_INT32: dsize=sizeof(long); break;
    case FT_STR:
    case FT_DICTPTR: dsize=sizeof(char*);break;
    default: 
      fatal("Invalid argument to write_recblock.");
      dsize=0; /* Silence compiler warnings; this will never actually
		  be reached. */
    } 
  
  fi_temp[0].ftype=ftype;
  return write_recarray(base, dsize, numrec, fi_temp, offset);
}


/* ------------------------------------------------------------------- */
/* "Profiling" functions             */
/* Routines for timing code execution */
/* These will only work on POSIX systems */

#ifdef PROFILE_SUPPORT 

static struct tms start;
clock_t start_realtime;
static struct tms delta;
clock_t delta_realtime;

void resetwatch(void)
{
  delta.tms_utime=delta.tms_stime=delta.tms_cutime=delta.tms_cstime=0;
  delta_realtime=0;
  start_realtime=times(&start);
}

void startwatch(void)
{
  start_realtime=times(&start);
}

static char watchbuff[81];
char *timestring(void)
{
  sprintf(watchbuff,"User:%ld.%02ld   Sys:%ld.%02ld   Total:%ld.%02ld"
	  "   Real:%ld.%02ld",
	  delta.tms_utime/100, delta.tms_utime%100,
	  delta.tms_stime/100, delta.tms_stime%100,
	  (delta.tms_utime+delta.tms_stime)/100,
	  (delta.tms_utime+delta.tms_stime)%100,  
	  delta_realtime/100, delta_realtime%100
	  );
  return watchbuff;
}

char *stopwatch(void)
{
  struct tms curr;

  delta_realtime+=times(&curr)-start_realtime;
  delta.tms_utime+=(curr.tms_utime-start.tms_utime);
  delta.tms_stime+=(curr.tms_stime-start.tms_stime);
  delta.tms_cutime+=(curr.tms_cutime-start.tms_cutime);
  delta.tms_cstime+=(curr.tms_cstime-start.tms_cstime);
  return timestring();
}

/* 5+7+9+8+4*3+4*?? = 41+?? */

#endif /* PROFILE_SUPPORT */






