/*  metacommand.c-- handles metacommand execution         */
/* Copyright 1996,97    Robert Masenten                   */
/*                                                        */
/* This is part of the source for the AGiliTy             */


#include <assert.h>
#include <ctype.h>
#include <stdio.h>

#include "agtread.h"
#include "uagt.h"
#include "exec.h"

#define DEBUG_SCAN 1
#define MAX_REDIR 1000  /* Maximum number of redirects, to catch
			    infinite loops. If this is 0, allow infinitely
			    many */

/* ------------------------------------------------------------------- */
/* METACOMMAND ROUTINES  */
/*  Functions for scanning and decoding of metacommands */
/* ------------------------------------------------------------------- */





static word wordcode_fix(word w)  
/* For $ strings. Returns dictionary index. This is used by the metacommand
   redirection routines */
{
  if (aver<AGTME10) return w;
  if (w==ext_code[wdverb]) return syntbl[auxsyn[vb]];
  if (w==ext_code[wdnoun]) return it_name(dobj);
  if (w==ext_code[wdobject]) return it_name(iobj);
  if (w==ext_code[wdadjective]) return it_adj(dobj);
  if (w==ext_code[wdprep]) return prep;
  if (w==ext_code[wdname]) return it_name(actor); 
  return w;
}


static void objcode_fix(word nounword,word objword)  
/* For $ strings. Fixes object redirection if neccessary */
{
  int savedobj;
  parse_rec *saverec;

  savedobj=dobj; saverec=dobj_rec;
  if (nounword==ext_code[wdobject]) 
    {dobj=iobj; dobj_rec=iobj_rec;}
  if (objword==ext_code[wdnoun])
    {iobj=savedobj;iobj_rec=saverec;}
}

static int run_metacommand(int cnum)
/* Return 
      0 to go on to next metacommand, 
      1 to stop running metacommands,  and 
      2 to end the turn. 
      3 indicates that redirection has just occured 
      -2 means we're doing disambiguation and just hit an action token. */
{
  int ip;
  bool notflag; /* If we got a NOT */
  bool orflag;  /* If we are processing an or */
  bool ortrue; /* Indicates we're in part of an OR statement and
		 and earlier statement has evaluated true */
  bool blocktrue; /* Indicates whether the current or block has evalutate
		    true so far */
  bool failstate; /* We have failed and are only still running for the
		     sake of FailMessage */
  int op, argnum, optype;
  int r;

/* argnum, arg1, arg2 */

  if (DEBUG_AGT_CMD && !supress_debug)
    debug_head(cnum);

  orflag=0;notflag=0;

  r=-1;  /* i.e. "continue" running this metacommand */  
  failstate=0;
  for(ip=0;ip<command[cnum].cmdsize && !failstate;ip+=argnum+1)
    {
      /* At this point r is 0 or -1, depending on whether the last
       token was true or false; 0=false, -1=true */

      op=command[cnum].data[ip];
      if (op<0) {
	writeln("GAME ERROR: Negative token found.");
	return 2;
      } 
      optype=op/2048; /* Split op into operand proper and optype */
      op=op%2048;
      
      if (r==0 && op!=109) failstate=1;
        /* That is, if a condition has failed and we don't have an OR,
	   stop running this metacommand */

      if (op>=START_ACT)  /*Action */
	if (op>MAX_ACT) {
	  writeln("GAME ERROR: Illegal action token encountered");
	  return 2;
	} else {
	  if (op<WIN_ACT)
	    argnum=act_def[op-1000].argnum;
	  else argnum=0;
	  if (orflag) { /* Terminate OR statement */
	    ortrue=blocktrue||ortrue;
	    if (!ortrue) failstate=1; /* End this command */
	    orflag=0;
	  }
	} 
      else  /* Condition */
	if (op>MAX_COND) {
	  writeln("GAME ERROR: Illegal condition token encountered");
	  return 2;
	} else argnum=cond_def[op].argnum;

      if (optype!=0) { /* Correct argnum for NOUN and OBJECT args */
	argnum-=((optype&8)==8) + ((optype&2)==2);
	if (argnum<0) {
	  writeln("GAME ERROR: Token list corrupted.");
	  return 2;
	}
      }

      if (ip+argnum>=command[cnum].cmdsize) {
	writeln("GAME ERROR: Unexpected end of token sequence");
	return 2;
      }

      /* Now to support FailMessage and its siblings:
	 if the last condition didn't fail, we skip over them. */
      if (failstate)
	if (op>=1128 && op<=1131) 
	  { /* Do nothing */}
	else 
	  return 0;
      else 
	if (op>=1128 && op<=1131)  /* Skip them */
	  continue;
      
      if (op==108)  /* NOT */
	{
	  r=-1;
	  if (DEBUG_AGT_CMD && !supress_debug) debug_cmd_out(ip,op,0,0,optype);
	  notflag=1;
	}
      else if (op==109)  { /* OR */
	if (DEBUG_AGT_CMD && !supress_debug) debug_cmd_out(ip,op,0,0,optype);
	notflag=0;
	if (orflag)  /* Already in the middle of an or statement */
	  ortrue=ortrue||blocktrue;
	else ortrue=(r==-1);
	r=-1;  /* Continue executing... */
	blocktrue=1; /* The new block starts off true */
	orflag=1; /* We are in or-mode */
      } 
      else { /* Ordinary arguments */
	if (argnum==1 && ((optype&8)==8)) 
	  ip--; /* First arg is NOUN/OBJ, 2nd arg is real */
	switch(argnum) {
          case 0:r=exec_token(ip,op,0,0,optype);break;
	  case 1:r=exec_token(ip,op,command[cnum].data[ip+1],0,optype);
	    break;
	  case 2:r=exec_token(ip,op,command[cnum].data[ip+1],
			      command[cnum].data[ip+2],optype);break;
	  default: writeln("INTERNAL ERROR: Too many token arguments.");
	    exit(EXIT_FAILURE);
	  }      
	if (notflag) 
	  if (r==-1) r=0;
	  else if (r==0) r=-1;
	notflag=0;
      }

      /* The only way we can reach this point with failstate set is
	 if we were running FailMessage, in which case we actually want
	 to end the turn. */
      if (failstate) return 2;

      if (DEBUG_AGT_CMD && !supress_debug)
	if (r==-1) debugout("\n");
	else if (r==-2) debugout("==> ACTION\n");
	else 
	  if (op<START_ACT) debugout("--->FAIL\n");
	  else debugout("==> END\n");

      if (op==2002)  /* QuitThisCmd ignores NOT and OR */
       	return 0;
      if (r>0 || r==-2) return r;

      if (orflag) {
	blocktrue=blocktrue && (r==-1);
	  /* blocktrue is true only if all the conditions
	     in the current OR-block are true; the OR statement
	     will be true if one of the OR-blocks is true. */
	r=-1;
      }      
    }
  return 0;
}



static int cmatch(word w1,word w2)
/* This is used to match the elements of metacommand trigger patterns */
/* Sees if w2 matches pattern of w1. */
{
  if (w1==w2) return 1;
  if (w1==0 && w2!=ext_code[wall]) return 1; /* ANY */
      /* (But note that ANY does not match ALL) */
  return 0;
}

static int extract_actor(int actnum)
{
  if (actnum<0) actnum=-actnum;  /* Erase redirection stuff */
  if (tcreat(actnum)) return actnum;
  else return 0;
}


static int cm_actor(int actnum,int actor)
{
  if (actnum==1) return actor==0;
  if (tcreat(actnum)) return (actor==actnum);
  if (actnum==2) return (actor!=0);  /* ANYBODY? */
  return (actor==0);
}

static bool cm_verb(word vpat,word vmatch) 
{
  if (vpat!=0) return (vpat==vmatch);
  if (vmatch==0) return 1;
  return mars_fix;  /* ANY matchs <specific verb> only if mars_fix==1 */
}

#define cm(i,f,w) (cmatch(command[i].f,w))


static void scan_dbg(int vcode)
{
  char buff[200];

  sprintf(buff,"+++++Scanning %s\n",dict[syntbl[auxsyn[vcode]]]);
  debugout(buff);
}


int scan_metacommand(integer m_actor, int vcode,
		     word m_noun,word m_prep,word m_obj,
		     word m_nounadj, word m_objadj)
/* m_<word> are the matching criterion; they have no *neccessary*
  connection do dobj, iobj, etc. */
/* Return codes:  0=end of this cycle, 1=end of all commands
   2=end of turn */
/* If doing disambiguation, then -2=end of cycle, something happened;
   0 or 1=end of cycle; nothing happened; 2=end of turn, nothing happened. */
{
  int i;
  int scanstart, scanend;
  int redirect_count;  /* This is a safety measure: this keeps track of how 
			many redirections have occured on a single turn, and
			if there are "too many" it will issue an error message
			and stop. This is to prevent the system from getting
			into a redirection loop. The number should be set
			high enough not to prevent deliberate loops, 
			however. */
  redirect_count=0;  

  if (mars_fix && vcode==0) return 0; 
     /* Don't explicity scan ANY metacommands if MARS fix is active. */

  if (DEBUG_AGT_CMD && DEBUG_SCAN &&!supress_debug) scan_dbg(vcode);

  if (m_actor==0) {
    scanstart=verbptr[vcode];
    scanend=verbend[vcode];
  } else {
    scanstart=verbptr[DIR_ADDR_CODE];
    scanend=verbend[DIR_ADDR_CODE];
  }
  for(i=scanstart;i<scanend;i++)
    if ( command[i].actor<0) {/* REDIRECT data; skip over it */;}
    else if (
	     cm_verb(command[i].verbcmd,syntbl[auxsyn[vcode]]) 
	     && cm_actor(command[i].actor,m_actor)
	     && cm(i,nouncmd,m_noun) 
	     && cm(i,prep,m_prep)
	     && cm(i,objcmd,m_obj)
#ifdef ADJ_META
	     && cm(i,noun_adj,m_nounadj)
	     && cm(i,obj_adj,m_objadj)
#endif
	     )
      switch (run_metacommand(i))
	{
	case -2:return -2; /* We are doing disambiguation and reached
			      an action token */
	case 0:break; /* Go onto next metacommand */
	case 1:return 1;  /* Done with metacommands */
	case 2:return 2;  /* Done with turn */	  
	case 4:return 0; /* RETURN from subroutine */

	case 3:     /* Redirection */
	  if (++i==last_cmd || command[i].actor>0) {
	    writeln("GAME ERROR: Invalid REDIRECT token.");
	    return 2;
	  }
	  if (MAX_REDIR!=0 && ++redirect_count>MAX_REDIR) {
	    writeln("GAME ERROR: Infinite REDIRECT loop.");
	    return 2;
	  }
	  if (DEBUG_AGT_CMD && !supress_debug) {
	    debugout("   ==>");
	    debug_head(i);
	  }
	  /* Redirection is very superficial-- normally all it does is */
	  /* change the matching pattern, not the underlying objects */
	  /* The one exception is when we use the special redirection tokens */
	  /* NOUN or OBJECT */

	  m_actor=actor=extract_actor(command[i].actor);
	  vcode=vb=verb_code(wordcode_fix(command[i].verbcmd));
	  m_noun=wordcode_fix(command[i].nouncmd);
	  m_obj=wordcode_fix(command[i].objcmd);
	  m_prep=wordcode_fix(command[i].prep); 
#ifdef ADJ_META
	  m_nounadj=wordcode_fix(command[i].noun_adj);
	  m_objadj=wordcode_fix(command[i].obj_daj);
#endif
	  objcode_fix(command[i].nouncmd,command[i].objcmd);
	  ignore_grammer=1;  /* Should really do better than this */

	  if (m_actor==0) {
	    if (!mars_fix) /* In MARS, we *don't* go back to the top */
	      i=verbptr[vcode]-1;
	    scanend=verbend[vcode];
	  } else   /* actor case */
	    if (!mars_fix)  
	      i=verbptr[DIR_ADDR_CODE]-1;
 	 /* So when i is incremented, we start back at the correct start: i.e.
	    we start scanning again from the beginning. It's even possible
	    to use REDIRECT to run verb commands from an AFTER command,
	    although it precludes other AFTER commands from running. */
	  break;
	}
  return 0; /* Done with this cycle of metacommands */
}

#undef cm





