/*  auxfile.c--  Routines for reading and parsing the various secondary */
/*    data files:  OPT, VOC, TTL, INS, CFG                    */        
/* 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.    */

#include <assert.h>
#include <ctype.h>
#include "agtread.h"

/* ------------------------------------------------------------------- */
/* Purity flag initialization                                          */
/*    Logically, these belong in agtdata.c, but I wanted to keep them  */
/*    near the CFG reading routines.                                   */
/* ------------------------------------------------------------------- */
/*   The following are AGT 'purity' flags; they turn off features of */
/* my interpreter that are not fully consistent with the original AGT */
/* and so could break some games. Some of these are trivial improvements; */
/* some are more radical and should be used with caution. Several are */
/* only useful if a game was designed with them in mind. */
/*   In all cases, setting the flag to 1 more closely follows the */
/* behavior of the original interpreters */
/* WARNING: Many of these haven't been tested extenstivly in the non-default 
   state. */ 


bool PURE_ANSWER=0; /* For ME questions, requires that AND-separated
			  answers be in the same order in the player's
			 answer as they are in the game file. According
			 to the AGT documentation, AND should ignore 
			 the order, but the original AGT interpreters
			 (at least the one I've tested) don't conform 
			 to this. */

bool PURE_TIME=1; /* Set to 0 causes time to always be increased 
			    by delta_time rather than by a random amount
			between 0 and delta_time. Only really of any use
			to a game author who wanted to write a game
			explicitly for AGiliTy. */

/* bool PURE_BOLD=1; Set to 0 causes the backslash to toggle bold on and
			off for all versions of AGT, not just 1.8x.
			I can think of no reason to do this unless
			you are an AGT author who wants to use the 1.8x 
			bold feature with the Master's Edition compiler. */

bool PURE_AND=1; /* increment the turn counter for each noun in a
 	            chain of <noun> AND <noun> AND ... If 0, the turn
		     counter will only be incremented by one in such a case. 
		     (need to do something about metacommands, as well...) */

bool PURE_METAVERB=1; /* If set, ANY and AFTER commands are run even
			   if you type in a metaverb (SAVE, RESTORE,...
			   that is, any verb that doesn't cause time to
			   pass). Verb specific metacommands are _always_ 
			   run. */

bool PURE_ROOMTITLE=1;  /* If 0, the interpreter will print out room
			   names before room descriptions even for 
			   pre-ME games */			 

bool PURE_SYN=0; /* Treats synonyms as nouns when parsing: that is, they
		       must show up only as the last word and they have the
		       same priority as noun matches during disambiguation.
		      If this is 0, then synonyms can appear anywhere in 
		       the name the player types in but are still
		       disambiguated as nouns. */

bool PURE_NOUN=0; /* _Requires_ a noun to end a word. This is only
			 imperfectly supported: if there are no other 
			 possible matches the parser will take the adjective-
			 only one anyhow. Frankly, I can't think of any reason
			 to set this to 1, but it's included for completeness
			 sake (and for any AGT Purists out there :-) ) */

bool PURE_ADJ=1; /* Picks noun/syn-matches over pure adj matches
		       when disambiguating. This is redundant if PURE_NOUN=1
		       since in that case pure adjective matches will
		       be rejected anyhow. */

bool PURE_DUMMY=0;  /* If set, the player can running dummy verbs
			 in the game by typing 'dummy_verb3'; otherwise,
			 this will produce an error message */

bool PURE_SUBNAME=0; /* If set, the player can run subroutines from
			  the parse line by typing (e.g.) 'subroutine4' 
			  (yes, the original AGT interpreters actually
			  allow this). If cleared, this cheat isn't
			  available */
bool PURE_PROSUB=0;  /* If clear, then $you$ substitutions are done
			everywhere $$ substitutions are, even in 
			messages written by the game author.
			If set, these substitutions are only made
			in internal game messages */

bool PURE_HOSTILE=1;  /* =0 Will allow you to leave a room with a hostile
			   creature if you go back the way you came */
bool PURE_ALL=1;        /* =0 will cause the parser to expand ALL */
bool PURE_DISAMBIG=1;   /* =0 will cause intelligent disambiguation */
bool PURE_GETHOSTILE=1;  /* =0 will prevent the player from picking things
			    up in a room with a hostile creature */

bool PURE_OBJ_DESC=1;    /* =0 prevents [providing light] messages
			  from being shown */

/*-------------------------------------------------------------------------*/
/* .CFG reading routines                                                   */
/*-------------------------------------------------------------------------*/

/* The main interpreter handles configuration in this order:
   1) Global configuration file
   2) First pass through game specific CFG to get the settings for
       SLASH_BOLD and IBM_CHAR which we need to know _before_ reading
       in the game.
   3) Read in the game.
   4) Main pass through game specific CFG. Doing it here ensures that
      its settings will override those in the gamefile.
  Secondary programs (such as agt2agx) usually only call this once, for
  the game specific configuration file.
      */

#define opt(s) (strcasecmp(optstr[0],s)==0)

static void cfg_option(int optnum,char *optstr[], bool lastpass)
/* This is passed each of the options; it is responsible for parsing
   them or passing them on to the platform-specific option handler
   agt_option() */
/* lastpass is set if it is the last pass through this configuration
   file; it is false only on the first pass through the game specific
   configuration file during the run of the main interpreter */
{
  bool setflag;

  if (optnum==0 || optstr[0]==NULL) return;

  if (strncasecmp(optstr[0],"no_",3)==0) {
    optstr[0]+=3;
    setflag=0;
  } else setflag=1;

  if (opt("slash_bold")) bold_mode=setflag;
  else if (!lastpass) { 
    /* On the first pass, we ignore all but a few options */
    agil_option(optnum,optstr,setflag,lastpass); 
    return;
  }
  else if (opt("irun")) irun_mode=setflag;
  else if (opt("block_hostile")) PURE_HOSTILE=setflag;
  else if (opt("get_hostile")) PURE_GETHOSTILE=setflag;
  else if (opt("debug")) {if (aver<=AGTME10) debug_mode=setflag;}
  else if (opt("pure_answer")) PURE_ANSWER=setflag;
  else if (opt("const_time")) PURE_TIME=!setflag;
  else if (opt("fix_multinoun")) PURE_AND=!setflag;
  else if (opt("fix_metaverb")) PURE_METAVERB=!setflag;
  else if (opt("roomtitle")) PURE_ROOMTITLE=!setflag;
  else if (opt("pure_synonym")) PURE_SYN=setflag;
  else if (opt("adj_noun")) PURE_ADJ=!setflag;
  else if (opt("pure_dummy")) PURE_DUMMY=setflag;
  else if (opt("pure_subroutine")) PURE_SUBNAME=setflag;
  else if (opt("pronoun_subs")) PURE_PROSUB=!setflag;
  else if (opt("verbose")) verboseflag=setflag;
  else if (opt("alt_any")) mars_fix=setflag;
  else if (opt("smart_disambig")) PURE_DISAMBIG=!setflag;
  else if (opt("expand_all")) PURE_ALL=!setflag;
  else if (opt("object_notes")) PURE_OBJ_DESC=setflag;
  else agil_option(optnum,optstr,setflag,lastpass);
}

#undef opt

/* Returns false if it there are too many tokens on the line */
bool parse_config_line(char *buff, bool lastpass)
{
  char *opt[50], *p;
  int optc;
  
  optc=0;
  opt[0]=NULL;
  for(p=buff;*p;p++) {
    if (isspace(*p)) {  /* Whitespace */
      if (opt[optc]!=NULL) {/*... which means this is the first whitespace */
	if (optc==50) return 0; /* Too many */
	opt[++optc]=NULL;
      }
      *p=0;
    } else  /* No whitespace */
      if (opt[optc]==NULL) /* ...this is the first non-whitespace */
	opt[optc]=p;
  }
  if (opt[optc]!=NULL) opt[++optc]=NULL;
  cfg_option(optc,opt,lastpass);
  return 1;
}


/* For the meaning of lastpass, see comments to cfg_option() above */
void read_config(FILE *cfgfile, bool lastpass)
{
  char buff[100];

  if (cfgfile==NULL) return;

  while(readln(cfgfile,buff,99)) {
    if (buff[0]=='#') continue; /* Comments */
    /* Now we parse the line into words, with opt[] pointing at the words
       and optc counting how many there are. */  
    if (!parse_config_line(buff,lastpass))
      rprintf("Too many tokens on configuration line.\n");
  }
  fclose(cfgfile);
}



/*-------------------------------------------------------------------------*/
/* Read OPT file                                                          */
/*  (most of these routines used to be in agil.c)                          */
/*-------------------------------------------------------------------------*/

/* .OPT reading routines */
/* I've put the comments on the format here because they don't really
   belong anywhere else. (Maybe in agtread.h, but I don't want to further 
   clutter that already quite cluttered file with something as peripheral
   as this) */
/* OPT file format:  the .OPT file consists of 14 bytes. They are:
   0  Screen size(0=43/50 rows, 1=25 rows)
   1  Status line(1=top, 0=none, -1=bottom)
   2  Unknown, always seems to be 0
   3  Put box around status line?
   4  Sound on?
   5  Menus on?
   6  Fixed input line?
   7  Print transcript?
   8  Height of menus (3, 4, 5, 6, 7, or 8)
   9  Unknown, always seems to be 0
   10-13  Color scheme: output/status/input/menu, specified in DOS attribute
      format (Bbbbffff,  B=blink, b=backround, f=foreground, 
      MSB of foreground specifies intensity ("bold") ). */
/* The interpreter ignores almost all of this. */
   
void read_opt(char *fname)
{
  FILE *optfile;

  have_opt=0;
  optfile=openfile(fname,pOPT,NULL,0); 
  if (optfile!=NULL) {
    if (fread(opt_data,14,1,optfile)!=1) fatal("Invalid OPT file.");
    have_opt=1;
  }
}


/*-------------------------------------------------------------------------*/
/* Read and process TTL                                                    */
/*  (most of these routines used to be in agil.c)                          */
/*-------------------------------------------------------------------------*/

/* Shades of Gray uses a custom interpreter that prints out the names
   of the authors as the program loads. */
/* Normally I wouldn't bother with this, but Shades of Gray is probably
   the best known of all AGT games */

#define SOGCREDIT 7
static const char *sogauthor[SOGCREDIT]={
  "Mark \"Sam\" Baker",
  "Steve \"Aaargh\" Bauman",
  "Belisana \"The\" Magnificent",
  "Mike \"of Locksley\" Laskey",
  "Judith \"Teela Brown\" Pintar",
  "Hercules \"The Loyal\" SysOp",
  "Cindy \"Nearly Amelia\" Yans"
};

static bool check_dollar(char *s)
/* Determines if s consists of an empty string with a single dollar sign
 and possibly whitespace */
{
  bool dfound;
  dfound=0;
  for(;*s!=0;s++)
    if (*s=='$' && !dfound) dfound=1;
    else if (!isspace(*s)) return 0;
  return dfound;
}

descr_line *read_ttl(char *fname)
{
  FILE *ttlfile;
  int i, j, height;
  descr_line *buff; 


  ttlfile=openfile(fname,pTTL,"Warning: Could not open title file '%s'.",0);
  if (!ttlfile) return NULL;

  buff=rmalloc(sizeof(descr_line));
  i=0;
  while( NULL!=(buff[i]=readln(ttlfile,NULL,0)) ) {
    if (strncmp(buff[i],"END OF FILE",11)==0) break;
    else if (aver>=AGT18 && aver<=AGT18MAX && check_dollar(buff[i]))
      statusmode=4;
    else {
      for(j=0;buff[i][j]!=0;j++) 
	buff[i][j]=fixchar[(uchar)buff[i][j]];
      /* Advance i and set the next pointer to NULL */
      buff=rrealloc(buff,sizeof(descr_line)*(++i + 1));
      buff[i]=NULL;
    }
    rfree(buff[i]); 
  }

  rfree(buff[i]);
  while(buff[i]==NULL || strlen(buff[i])<=1) { /* Discard 'empty' lines */
    if (i==0) break;
    rfree(buff[i]);
    i--;
  } 
  height=i;

  if (aver==AGTCOS && ver==4 && height>=17) /* SOGGY */ 
    for(i=0;i<SOGCREDIT;i++) 
      if (strlen(sogauthor[i])+9+i<strlen(buff[i+7]))
	memcpy(buff[i+7]+9+i,sogauthor[i],strlen(sogauthor[i]));

  return buff;
}

void free_ttl(descr_line *title)
{
  int i;
  if (title==NULL) return;
  for(i=0;title[i]!=NULL;i++)
    rfree(title[i]);
  rfree(title);
}


/*-------------------------------------------------------------------------*/
/* Read and convert VOC                                                    */
/*  (most of these routines used to be in agil.c)                          */
/*-------------------------------------------------------------------------*/


static char *newvoc[]={"1 Menu","1 Restart","1 Undo"};
static int newindex=0; /* Points into newvoc */

void add_verbrec(char *verbline,bool addnew)
{
  char s[3],*p;

  while(isspace(*verbline)) verbline++;
  if (*verbline==0 || verbline[0]=='!') return; /* Comment or empty line */

  /* The following guarentees automatic initialization of the
     verbrec structures */
  if (!addnew) 
    while (newindex<3 && strcasecmp(verbline+2,newvoc[newindex]+2)>0) 
      add_verbrec(newvoc[newindex++],1);
  
  verbinfo=rrealloc(verbinfo,(vm_size+1)*sizeof(verbentry_rec));

  s[0]=verbline[0];s[1]=0;
  verbinfo[vm_size].objnum=strtol(s,NULL,10)-1;

  verbline+=2; /* Skip over number */

  verbinfo[vm_size].verb=verbinfo[vm_size].prep=0;
  p=verbline;
  if (*p!=0) {
    while(*p && !isspace(*p)) p++;
    if (*p!=0) {
      *p=0;
      p++;
    }
    verbinfo[vm_size].verb=search_dict(verbline);
    if (verbinfo[vm_size].verb==-1) {
      verbinfo[vm_size].verb=0;return;}
    if (*p!=0) {
      verbinfo[vm_size].prep=search_dict(p);
      if (verbinfo[vm_size].prep==-1) 
	verbinfo[vm_size].prep=0;
    }
  }
  vm_size++;
}

void init_verbrec(void)
/* Need to insert special verbs into verbinfo */
/* Fill in vnum field */
/* UNDO, RESTART, MENU  */
{
  verbinfo=NULL;
  vm_size=0;  
  newindex=0;
  if (freeze_mode) newindex=1;  /* Don't include MENU option if we can't
				   use it. */
}

void finish_verbrec(void)
{
  for(;newindex<3;newindex++) add_verbrec(newvoc[newindex],1);
}


void read_voc(char *fname)
{
  char linbuf[80];
  FILE *vocfile;

  init_verbrec();
  vocfile=openfile(fname,pVOC,NULL,0); 
  if (vocfile!=NULL) {  /* Vocabulary file exists */  
    while(readln(vocfile,linbuf,79))
      add_verbrec(linbuf,0);
    fclose(vocfile);
    finish_verbrec();
  }
}




/*-------------------------------------------------------------------------*/
/* Read INS file                                                           */
/*  (most of these routines used to be in agil.c)                          */
/*-------------------------------------------------------------------------*/


static FILE *insfile=NULL;
static char *ins_buff;

static descr_line *ins_descr=NULL;
static int ins_line;  /* Current instruction line */


/* Return 1 on success, 0 on failure */
bool open_ins_file(char *fname,bool report_error)
{
  ins_buff=NULL;

  if (ins_descr!=NULL) return 1;

  if (insfile!=NULL) {
    rewind(insfile);
    return 1;
  }

  if (agx_file) {
    ins_descr=read_descr(ins_ptr.start,ins_ptr.size);
    ins_line=0;
    if (ins_descr!=NULL) return 1;

    /* Note that if the AGX file doesn't contain an INS block, we 
       don't immediatly give up but try opening <fname>.INS */
  }

  insfile=openfile(fname,pINS,
		   report_error  
   		     ? "Sorry, Instructions aren't available for this game" 
		     : NULL,
		   0);
  return(insfile!=NULL);
}

char *read_ins_line(void)
{
  if (ins_descr) {
    if (ins_descr[ins_line]!=NULL)
      return ins_descr[ins_line++];
    else return NULL;
  } else {
    rfree(ins_buff);
    ins_buff=readln(insfile,NULL,0);
    return ins_buff;
  }
}

void close_ins_file(void)
{
  if (ins_descr) {
    free_descr(ins_descr);
    ins_descr=NULL;
  } else if (insfile) {
    rfree(ins_buff);
    fclose(insfile);
    insfile=NULL;
  }
}



descr_line *read_ins(char *gamename)
{
  descr_line *txt;
  char *buff;
  int i;
  
  i=0;
  txt=NULL;
  if (open_ins_file(gamename,0)) {   /* Instruction file exists */   
    while ( NULL!=(buff=read_ins_line()) ) {
      /* Enlarge txt; we use (i+2) here to leave space for the trailing \0 */
      txt=rrealloc(txt,sizeof(descr_ptr)*(i+2)); 
      txt[i++]=rstrdup(buff);
    }
    if (txt!=NULL)
      txt[i]=0; /* There is space for this since we used (i+2) above */
    close_ins_file();
  }
  return txt;
}


void free_ins(descr_line *instr)
{
  int i;
  if (instr==NULL) return;
  for(i=0;instr[i]!=NULL;i++)
    rfree(instr[i]);
  rfree(instr);
}



/* Character translation routines, used by agtread.c and read_ttl() */
void build_fixchar(void)
{
  int i;
  for(i=0;i<256;i++) {
    if (i=='\r' || i=='\n') fixchar[i]=' ';
    else if (i=='\\' && bold_mode) fixchar[i]=FORMAT_CODE;
    else if (i>=0x80 && fix_ascii_flag)
      fixchar[i]=trans_ibm[i & 0x7f];
    else if (i==0)  /* Fix color and blink codes */
      fixchar[i]=FORMAT_CODE;  
    else fixchar[i]=i;
  }
}


