/* rmem.c-- wrappers around memory allocation and other misc. */
/*         utilities                                      */
/* Copyright 1996,97    Robert Masenten                   */
/*                                                        */
/* 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 "agtread.h"

#ifdef force16
#undef int
#endif


#ifdef UNIX_IO
#include <fcntl.h>
#include <sys/stat.h>  /* Needed only for file permission bits */

#ifdef MSDOS
#include <io.h>
#endif

#endif /* UNIX_IO */

#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)
{
  bool 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)
{
  bool 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.                                        */
/*----------------------------------------------------------------------*/
 
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("MALLOC 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("MALLOC 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("REALLOC ERROR: Oversized structure requested.\n");
    exit(EXIT_FAILURE);
  }
  assert(size>=0);
  if (rm_acct && old==NULL) ralloc_cnt++;
  p=realloc(old,(size_t)size);
  if (p==NULL && rm_trap && size>0)
    {
      printf("REALLOC ERROR: Out of memory\n");
      exit(EXIT_FAILURE);
    }
  return p;
}

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

#ifdef HAVE_STRDUP
  t=strdup(s);
#else
  t=malloc((strlen(s)+1)*sizeof(char));
#endif
  if (t==NULL && rm_trap)
    {
      printf("STRDUP 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.         */
/*----------------------------------------------------------------------*/

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

char *concdup(const char *s1,const char *s2)
{
  char *s;
  s=rmalloc(sizeof(char)*(strlen(s1)+strlen(s2)+2));
  strcpy(s,s1);
  strcat(s," ");
  strcat(s,s2);
  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];  
}

/*----------------------------------------------------------------------*/
/* File name utilities: Utilities to construct file names with exts     */
/*----------------------------------------------------------------------*/

/* Add extension to filename */
char *make_fname(char *fname, char *ext)
{
  char *fname_ext;

  fname_ext=rmalloc(strlen(fname)+strlen(ext)+1);
#ifndef PREFIX_EXT
  strcpy(fname_ext,fname);
  strcat(fname_ext,ext);
#else
  strcpy(fname_ext,ext);
  strcat(fname_ext,fname);
#endif
  return fname_ext;
}

char *remake_fname(char *fname, char *ext)
{
  int fnleng, extleng;

  fnleng=strlen(fname);
  extleng=strlen(ext);
  fname=rrealloc(fname,fnleng+extleng+1);
#ifndef PREFIX_EXT
  strcat(fname,ext);
#else
  memmove(fname+fnleng,fname,fnleng+1);
  memcopy(fname,ext,extleng);
#endif
  return fname;
}

bool check_fname(char *fname, char *ext)
{
  int n;

#ifndef PREFIX_EXT
  n=strlen(fname)-strlen(ext);
  return (fnamecmp(fname+n,ext)==0);
#else
    /* May need to make this case insensitive on some platforms */
  return (fnamencmp(fname,ext,strlen(ext))==0);  
#endif
}

/* Remove unwanted extension, if it is there */
char *trunc_fname(char *fname, char *ext)
{
  int n, m;

  n=strlen(fname);
  m=strlen(ext);
  if (n==0 || m==0) return fname;
#ifndef PREFIX_EXT
  if (fnamecmp(fname+n-m,ext)==0) {
    fname=rstrdup(fname);
    fname[n-m]=0;
  }
#else 
  /* May need to make this case insensitive on some platforms */
  if (fnamencmp(fname,ext)==0)
    fname=rstrdup(fname+m);
#endif
  return fname;
}


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

long file_size(FILE *f)
/* Returns the size of a binary file */
{
  long pos, leng;

  pos=ftell(f);
  fseek(f,0,SEEK_END);
  leng=ftell(f);
  fseek(f,pos,SEEK_SET);
  return leng;
}


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

FILE *openfile(char *fname,char *ext,char *err,bool 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. */ 
{
  FILE *textfile;  /* Actually, this may not be a text file anymore */
  char *fname_ext;
  char *estring; /* Hold error string */

  fname_ext=make_fname(fname,ext);
 /* Open as binary so we can deal with platform differences in text file 
    format _explicitly_ */
#ifdef OPEN_AS_TEXT
  if (text_file && !open_as_binary)
    textfile=fopen(fname_ext,"r"); 
  else
#endif
    textfile=fopen(fname_ext,"rb"); 

  if (textfile==NULL && err!=NULL) { 
    estring=rmalloc(strlen(err)+strlen(fname_ext)+2);
    sprintf(estring,err,fname_ext);
    rfree(fname_ext);
    if (ferr) fatal(estring);
    else writeln(estring);
  }
  rfree(fname_ext);  
  return textfile;
}


/* 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(FILE *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=fgetc(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=fgetc(f);   
    if (newc!='\n') ungetc(newc,f); /* Replace the character we just read. */
  } else if (c==DOS_EOF)  /* Ctrl-Z is the DOS EOF marker */
    ungetc(c,f);    /* 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.                                                 */
/*-------------------------------------------------------------------------*/

#ifdef UNIX_IO
int bfd; /* File descriptor for buffered file */
#else
FILE *bfile; 
#endif

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 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(int recsize)
{
  record_size=recsize;
  buff_fcnt=buffsize/record_size;
  buff_frame=0;
    
#ifdef UNIX_IO
  lseek(bfd,block_offset,SEEK_SET);
  read(bfd,buffer,record_size*buff_fcnt);
#else
  fseek(bfile,block_offset,SEEK_SET);
  fread(buffer,record_size,buff_fcnt,bfile);
#endif
}


long buffopen(char *fname,char *ext,int minbuff,char *rectype,int 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 *fname_dan;
  char ebuff[200];

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

  fname_dan=make_fname(fname,ext);

#ifdef UNIX_IO
  bfd=open(fname_dan,READFLAG);
  if (bfd==-1) {
    if (rectype==NULL) return 0;
    sprintf(ebuff,"Cannot open %s file %s.",rectype,fname_dan);
    rfree(fname_dan);
    fatal(ebuff);}
  filesize=lseek(bfd,0,SEEK_END);
#else
  bfile=fopen(fname_dan,"rb");
  if (bfile==NULL) {
    if (rectype==NULL) return 0;
    sprintf(ebuff,"Cannot open %s file %s.",rectype,fname_dan);
    rfree(fname_dan);
    fatal(ebuff);
  }
  fseek(bfile,0,SEEK_END);
  filesize=ftell(bfile);
#endif

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

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

  buffer=rmalloc(buffsize); /* Might want to make this adaptive eventually */
  
  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;
  buff_setrecsize(recsize);
  if (!agx_file && DIAG) {
    rprintf("Reading %s file %s (size:%ld)\n",rectype,fname_dan,filesize);
    rprintf("  Record size=  Formal:%d    File:%ld",minbuff,recsize);
  }
  rfree(fname_dan);
  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;

  assert(buff_rsize<=record_size);
  if (index>=buff_frame && index<buff_frame+buff_fcnt)
    bptr=buffer+(index-buff_frame)*record_size;
  else {
#ifdef UNIX_IO
    if (lseek(bfd,block_offset+index*record_size,SEEK_SET)==-1)
      fatal(strerror(errno));
    if (read(bfd,buffer,record_size*buff_fcnt)==-1)
      fatal(strerror(errno));
#else
    if (fseek(bfile,block_offset+index*record_size,SEEK_SET)!=0)
      fatal(strerror(errno));
    errno=0;
    if (fread(buffer,record_size,buff_fcnt,bfile)!=buff_fcnt)
      if (errno==0) 
	fatal("Unexpected end of file.");
      else fatal(strerror(errno));
#endif
    buff_frame=index;
    bptr=buffer;
  }
  if (!agx_file) compute_sig(bptr);
  return bptr;
}

void buffclose(void)
{
#ifdef UNIX_IO
  if (close(bfd)==-1)
    fatal(strerror(errno));
#else
  if (fclose(bfile)==EOF)
    fatal(strerror(errno));
#endif
  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;

  block_offset=f_ofs;
  block_size=bl_size;

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

  rfree(buffer);
  buffer=rmalloc(buffsize); /* Resize the buffer */
  
  if (block_size % recnum !=0) {
      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;
  buff_setrecsize(recsize);
}


/*-------------------------------------------------------------------------*/
/* 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 int 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 int bw_fileleng;  /* Current file length */
#endif /* DEBUG_SEEK */

static char *bw_fname; /* File name we are writing to */

/* 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(char *fname, char *ext)
{
  char *fnameext;
  char ebuff[200];

  assert(buffer==NULL);

  fnameext=make_fname(fname,ext);

#ifdef UNIX_IO
  bfd=open(fnameext,WRITEFLAG,FILE_PERM);
  if (bfd==-1) {
    sprintf(ebuff,"Cannot open file %s: %s.",fnameext,strerror(errno));
    rfree(fnameext);
    fatal(ebuff);
  }
#else
  bfile=fopen(fnameext,"wb");
  if (bfile==NULL) {
    sprintf(ebuff,"Cannot open file %s: %s",fnameext,strerror(errno));
    rfree(fnameext);
    fatal(ebuff);
  }
#endif

  bw_last=0;
  buffsize=0;
  buffer=NULL;
  bw_fname=fnameext;
#ifdef DEBUG_SEEK
  bw_fileleng=0;
#endif
}

static void bw_seek(long offset)
{
#ifdef DEBUG_SEEK
  assert(offset<=bw_fileleng);
#endif
#ifdef UNIX_IO
  if (lseek(bfd,offset,SEEK_SET)==-1)
    fatal(strerror(errno));
#else
  if (fseek(bfile,offset,SEEK_SET)!=0)
    fatal(strerror(errno));
#endif
}


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);
#ifdef UNIX_IO
  if (write(bfd,buffer,record_size*(bw_last-bw_first))==-1) 
    fatal(strerror(errno));
#else
  if (fwrite(buffer,record_size,bw_last-bw_first,bfile)
      !=bw_last-bw_first)
    fatal(strerror(errno));
#endif  
#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() */
uchar *bw_getbuff(int 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);
  rfree(bw_fname);
#ifdef UNIX_IO
  if (close(bfd)==-1)
    fatal(strerror(errno));
#else
  if (fclose(bfile)==EOF)
    fatal(strerror(errno));
#endif
}

void bw_abort(void)
{
#ifdef UNIX_IO
  if (close(bfd)==-1)
    fatal(strerror(errno));
#else
  if (fclose(bfile)==EOF)
    fatal(strerror(errno));
#endif
  remove(bw_fname);
  rfree(bw_fname);
}


/*-------------------------------------------------------------------------*/
/* 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(int 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)
{
  if (int_buff!=NULL)
    memcpy((char*)buff,int_buff+offset,size);
  else {
#ifdef UNIX_IO
    if (lseek(bfd,offset,SEEK_SET)==-1)
      fatal(strerror(errno));
    if (read(bfd,buff,size)==-1)
      fatal(strerror(errno));
#else
    if (fseek(bfile,offset,SEEK_SET)!=0)
      fatal(strerror(errno));
    errno=0;
    if (fread(buff,size,1,bfile)!=1)
      if (errno==0) 
	fatal("Unexpected end of file.");
      else fatal(strerror(errno));
#endif
  }
}


/* 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);
#ifdef UNIX_IO
    if (write(bfd,buff,size)==-1) 
      fatal(strerror(errno));
#else
    if (fwrite(buff,size,1,bfile)!=1)
      fatal(strerror(errno));
#endif
#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, bool */
				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,0,1800,2000,3200,3500,8200,8300,8350,
		   5000,5050,5070,10000,15000,15500,16000,20000};

static int agx_decode_version(int vercode)
{
  if (vercode&1) /* Large/Soggy */
    if (vercode==3200) ver=4;
    else ver=2;
  else 
    if (vercode<10000) ver=1;
    else ver=3;
  switch(vercode&(~1)) 
    {
    case 0: 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 8200: return AGT182;
    case 8300: return AGT183A;
    case 8350: return AGT183;
    case 10000:return AGTME10;
    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;
  bool 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 boolean */
      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(bool)=filedata[0];
	break;
      case FT_BOOL: 
	*p(bool)=( (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 && s[i]!=0; 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 boolean */
      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(bool)?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 && s[i]!=0; 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, int eltsize, int numelts, 
		   file_info *field_info, char *rectype,
		   long file_offset, long file_blocksize) 
{
  int 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, int eltsize, int numelts, 
		   file_info *field_info, long file_offset) 
{
  int 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,int 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_INT32: dsize=sizeof(long); break;
    case FT_STR:
    case FT_DICTPTR: dsize=sizeof(char*);break;
    default: fatal("Invalid argument to read_recblock.");
    } 

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


long write_recblock(void *base,int ftype,int 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.");
    } 
  
  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 */






