/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "glk/agt/agility.h"
#include "glk/agt/interp.h"
#include "glk/agt/exec.h"

namespace Glk {
namespace AGT {

/* This contains the code for scanning and running metacommands.
   Note that while the code here deals with all of the flow-of-execution
   details, the code for executing individual tokens is actually
   in token.c (With a few exceptions for commands that impact
   the order of execution).  */

#define DEBUG_SCAN 1

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

#define MAX_SUBCALL 2047  /* Maximum number of subroutine calls.
				 If this is 0, no limit (except for the
				 program's stack size). */


/*

scan_metacommand
 -2=end of cycle, something happened (disambiguation only)
  0=end of this cycle   (disambig: end of cycle, nothing happened)
  1=end of all commands (disambig: end of cycle, nothing happened)
  2=end of turn         (disambig: nothing happened)

run_metacommand
	0 to go on to next metacommand,
	1 to stop running metacommands,  and
	2 to end the turn.
	3 indicates that redirection has just occurred
	4 indicates a subcall has just occurred.
	5 to go on to next metacommand after a return has occurred.
	-2 means we're doing disambiguation and just hit an action token.

*/


/* ====================================================================*/
/*  RUN METACOMMAND: The following are the routines used to execute */
/*   a single metacommand block.  run_metacommand is invoked by */
/*   scan_metacommand, which is further down in this file.  */
/* ====================================================================*/

/* ------------------------------------------------------------------- */
/*  TYPE CHECKING ROUTINES        */
/*  Routines used to do type checking for metacommands. */
/* ------------------------------------------------------------------- */


rbool argvalid(int argtype, int arg) {
	if (argtype & AGT_VAR) { /* We have a variable */
		/* First, verify that arg actually indexes a variable */
		if (arg < 0 || arg > VAR_NUM) return 0; /* Nope */

		if (argtype == AGT_VAR) return 1; /* Pure variable; contents don't matter */

		/* Next, verify its contents, using the rest of this routine */
		arg = agt_var[arg];
		argtype &= ~AGT_VAR; /* Mask off AGT_VAR */
	}

	if (argtype < 128) {
		if (tnoun(arg)) return (argtype & AGT_ITEM) != 0;
		if (troom(arg)) return (argtype & AGT_ROOM) != 0;
		if (arg == 0) return (argtype & AGT_NONE) != 0;
		if (arg == 1) return (argtype & AGT_SELF) != 0;
		if (tcreat(arg)) return (argtype & AGT_CREAT) != 0;
		if (arg == 1000) return (argtype & AGT_WORN) != 0;
		return 0;
	} else switch (argtype) {
		case AGT_NUM:
			return 1;
		case AGT_DIR:
			return (arg >= 1 && arg <= 12);
		case AGT_FLAG:
			return (arg >= 0 && arg <= FLAG_NUM);
		case AGT_CNT:
			return (arg >= 0 && arg <= CNT_NUM);
		case AGT_QUEST:
			return (arg >= 1 && arg <= MaxQuestion);
		case AGT_MSG:
			return (arg >= 1 && arg <= last_message);
		case AGT_ERR:
			return (arg >= 1 && arg <= NUM_ERR);
		case AGT_STR:
			return (arg >= 1 && arg <= MAX_USTR);
		case AGT_SUB:
			return (arg >= 1 && arg <= MAX_SUB);
		case AGT_PIC:
			return (arg >= 1 && arg <= maxpict);
		case AGT_PIX:
			return (arg >= 1 && arg <= maxpix);
		case AGT_FONT:
			return (arg >= 1 && arg <= maxfont);
		case AGT_SONG:
			return (arg >= 1 && arg <= maxsong);
		case AGT_ROOMFLAG:
			return (arg >= 1 && arg <= 32);
		case AGT_EXIT:
			return (argvalid(AGT_ROOM | AGT_NONE, arg)
			        || argvalid(AGT_MSG, arg - exitmsg_base)
			        || (arg < 0 && aver >= AGX00)); /* Treat as verb */
		case AGT_OBJFLAG:
			return (arg >= 0 && arg < oflag_cnt);
		case AGT_OBJPROP:
			return (arg >= 0 && arg < oprop_cnt);
		case AGT_ATTR:  /* ATTR and PROP are type-checked elsewhere */
		case AGT_PROP:
			return 1;
		default:
			writeln("INTERNAL ERROR:Unrecognized type specifier.");
		}
	return 0;
}

/* <special> is set true for NOUN, OBJECT, NAME variables that are 0 */
/* (In this case, some error handling is suppressed) */

static rbool argfix(int argtype, int *arg, int optype, rbool *special) {
	*special = 0;
	switch (optype) {
	case 0:
		break;  /* Direct: The easy case */
	case 1:  /* Variable */
		if (*arg == -1) { /* Top-of-stack */
			*arg = pop_expr_stack();
			break;
		}
		if (!argvalid(AGT_VAR, *arg)) return 0;
		*arg = (int)agt_var[*arg];
		break;
	case 2:
		*arg = dobj;
		*special = (dobj == 0);
		break; /* NOUN */
	case 3:
		*arg = iobj;
		*special = (iobj == 0);
		break; /* OBJECT */
	default:
		rprintf("Internal error: Invalid optype.");
		return 0;
	}
	if (!(optype & 2)) {
		/* i.e. we have direct or variable type */
		/* The noun and object types below are useless for direct use,
		   but may be useful when used as values of variables. */
		if (argtype < 64) {
			if (*arg == -1) { /* NAME */
				*arg = actor;
				*special = (actor == 0);
			} else if (*arg == -2) { /* NOUN */
				*arg = dobj;
				*special = (dobj == 0);
			} else if (*arg == -3) { /* OBJECT */
				*arg = iobj;
				*special = (iobj == 0);
			}
		}
	}
	return argvalid(argtype, *arg);
}


/* These are handled in the order ARG2 then ARG1 so that
   top-of-stack references will pop the stack in that order
   (so that the push-order will corrospond to the argument order) */
/* <grammar_arg> is true if "bad" argument is NOUN/OBJECT/etc. and
   is 0. */
static int argok(const opdef *opdata, int *arg1, int *arg2, int optype,
				 rbool *grammar_arg) {
	if ((opdata->argnum) > 1 && !argfix(opdata->arg2, arg2, optype % 4, grammar_arg))
		return 0;
	if ((opdata->argnum) > 0 && !argfix(opdata->arg1, arg1, optype / 4, grammar_arg))
		return 0;
	return 1;
}

/* ------------------------------------------------------------------- */
/*  INSTRUCTION DECODING ROUTINES  */
/*  Routines for decoding opcodes and their arguments */
/* ------------------------------------------------------------------- */

static int decode_instr(op_rec *oprec, const integer *data, int maxleng) {
	integer op_;
	int optype;
	int leng;
	rbool special_arg1;  /* Is the first argument a special 0-length argument? */

	oprec->negate = oprec->failmsg = oprec->disambig = 0;
	oprec->errmsg = nullptr;
	oprec->op = -1;
	oprec->opdata = &illegal_def;
	oprec->argcnt = 0;
	oprec->endor = 1;

	special_arg1 = 0;

	if (maxleng <= 0) {
		oprec->errmsg = "GAME ERROR: Unexpected end of token sequence.";
		return 1;
	}
	op_ = data[0];
	if (op_ < 0) {
		oprec->errmsg = "GAME ERROR: Negative token found.";
		return 1;
	}
	oprec->optype = optype = op_ / 2048; /* Split op_ into operand proper and optype */
	oprec->op = op_ = op_ % 2048;
	oprec->opdata = get_opdef(op_);

	if (oprec->opdata == &illegal_def) {
		if (op_ < START_ACT)
			oprec->errmsg = "GAME ERROR: Illegal condition token encountered.";
		else
			oprec->errmsg = "GAME ERROR: Illegal action token encountered.";
		return 1;
	}

	if (op_ < 1000) oprec->endor = 0; /* Conditional tokens don't end OR block */

	/* Recall that oprec->disambig is initialized to 0 */
	switch (op_) {
	case 89:
	case 95:
	case 96:
	case 97:
		oprec->disambig = 1;
		break; /* YesNo and Chance */
	case WIN_ACT:
	case WIN_ACT+1:
		oprec->disambig = 1;
		break; /* WinGame, EndGame */

	case 1037:
	case 1038: /* DoSubroutine, Return */
	case 1062:
	case 1115: /* RedirectTo, SetDisambigPriority */
	case 1132:            /* AND */
	case 1149:
	case 1150: /* Goto and OnFailGoto */
	case 1151:            /* EndDisambig */
	case 1152:            /* XRedirect */
		break;   /* Accept default of 0: these tokens don' trigger disambig */

	case 1135:
	case 1137:
	case 1138:
	case 1139:
	case 1140:
	case 1141:
	case 1142:
	case 1143:
	case 1147:
	case 1159:
		oprec->endor = 0;
		break;  /* Operations that only affect the stack don't
		 stop disambiguation, either. They also
		 don't mark the end of an OR block */

	default:
		/* Aside from the above exceptions, all actions will stop
		disambiguation (with success) and all conditions will let it
		 continue. */
		oprec->disambig = (op_ >= START_ACT && op_ < WIN_ACT);
	}

	if (op_ >= 1128 && op_ <= 1131) /* FailMessage group */
		oprec->failmsg = 1;

	leng = oprec->opdata->argnum + 1;
	if (optype != 0) { /* Correct leng for NOUN and OBJECT args */
		special_arg1 = ((optype & 8) == 8);
		leng -= special_arg1 + ((optype & 2) == 2);
		if (leng < 1) {
			oprec->errmsg = "GAME ERROR: Token list corrupted.";
			return 1;
		}
	}
	if (leng > maxleng) {
		oprec->errmsg = "GAME ERROR: Unexpected end of token sequence";
		return 1;
	}

	if (op_ == 108) { /* NOT */
		leng = 1 + decode_instr(oprec, data + 1, maxleng - 1);
		oprec->negate = !oprec->negate;
		return leng;
	}
	oprec->argcnt = leng - 1;
	oprec->arg1 = oprec->arg2 = 0;
	if (leng >= 2) {
		if (special_arg1) {
			assert(leng == 2);
			oprec->arg2 = data[1];
			oprec->arg1 = 0;
		} else oprec->arg1 = data[1];
	}
	if (leng >= 3) oprec->arg2 = data[2];
	if (leng >= 4) writeln("INTERNAL ERROR: Too many token arguments.");
	return leng;
}


/* decode_args checks and decodes the arguments to metacommand tokens */
/* Returns false on an error */
static rbool decode_args(int ip_, op_rec *oprec) {
	rbool grammar_arg; /* Have NOUN/OBJECT that is 0 and so failed argok tests */

	if (oprec->errmsg != nullptr) {
		if (!PURE_ERROR)
			writeln(oprec->errmsg);
		return 0;
	}
	if (DEBUG_AGT_CMD && !suppress_debug) {
		if (oprec->negate) { /* Output NOT */
			debug_cmd_out(ip_, 108, 0, 0, 0);
			ip_++;
		}
	}

	if (DEBUG_AGT_CMD && !suppress_debug)
		debug_cmd_out(ip_, oprec->op, oprec->arg1, oprec->arg2, oprec->optype);

	/* This checks and translates the arguments */
	if (!argok(oprec->opdata, &(oprec->arg1), &(oprec->arg2),
	           oprec->optype, &grammar_arg)) {
		/* Don't report errors for null NOUN/OBJECT/ACTOR arguments
		   used in conditional tokens */
		if (grammar_arg && oprec->op <= MAX_COND)
			return 0;
		if (!PURE_ERROR) {
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("\n");
			writeln("GAME ERROR: Invalid argument to metacommand token.");
		}
		return 0;
	}
	return 1;
}




/* ------------------------------------------------------------------- */
/*  Subroutine Call Stack routines                                     */
/* ------------------------------------------------------------------- */
/* Note: run_metacommand() passes subroutine calls up to it's parent,
   but it processes Returns on its own (and is the routine responsible
   for maintaining the subcall stack--  scan_metacommand treats
   a subroutine call just like RedirecTo) */
/* The progression for subroutine calls goes like this:
	run_metacommand hits a DoSubroutine token;
	  the subroutine id is saved in subcall_arg by exec_token.
	run_metacommand does push_subcall, saving cnum and ip,
	  and then returns 4 to scan_metacommand.
	scan_metacommand saves grammar state to the new stack entry
	  with push_subcall and then starts scanning SUBROUTINEnn

	Many tokens are executed.

	run_metacommand hits Return. It sets restart_state and
	  returns 5 to its parent.
	scan_metacommand then runs pop_subcall_grammar and restores
	  the original scanning grammar. It subtracts one from cnum
	  so the original cnum will be rerun.
	run_metacommand sees that restart_state is set and pops the
	  rest of the information (cnum and ip) off of the stack.
	Things continue as usual.
	*/



typedef struct {
	/* run_metacommand state */
	short cnum, ip, failaddr;
	/* scan_metacommand state */
	integer mactor, mdobj, miobj;
	word mprep;
	short vcode;
	/* Global state (is this really saved?) */
	short vb;
	word prep;
} subcall_rec;


static subcall_rec *substack = nullptr;
static short subcnt = 0;
static short subsize = 0;


static rbool push_subcall(int cnum, int ip_, int failaddr) {
	subcall_rec *savestack; /* In case something goes wrong. */

	if (MAX_SUBCALL != 0 && ++subcnt > MAX_SUBCALL)
		return 0;
	if (subcnt > subsize) {
		subsize += 5;
		savestack = substack;
		rm_trap = 0;
		substack = (subcall_rec *)rrealloc(substack, subsize * sizeof(subcall_rec));
		rm_trap = 1;
		if (substack == nullptr) { /* out of memory */
			substack = savestack;
			return 0;
		}
	}
	substack[subcnt - 1].cnum = cnum;
	substack[subcnt - 1].ip = ip_;
	substack[subcnt - 1].failaddr = failaddr;
	return 1;
}


/* pop_subcall_grammar is called before this */
static void pop_subcall(int *rcnum, int *rip, int *rfailaddr) {
	assert(*rcnum == substack[subcnt - 1].cnum);
	/* *rcnum=substack[subcnt-1].cnum; */
	*rip = substack[subcnt - 1].ip;
	*rfailaddr = substack[subcnt - 1].failaddr;
	subcnt--;
}

/* This is called after push_subcall */
static void push_subcall_grammar(int m_actor, int vcode, int m_dobj, word m_prep,
								 int m_iobj, int cnum) {
	/* run_metacommand should already have pushed cnum on the stack */
	substack[subcnt - 1].vb = vb;
	substack[subcnt - 1].prep = prep;
	substack[subcnt - 1].mactor = m_actor;
	substack[subcnt - 1].vcode = vcode;
	substack[subcnt - 1].mdobj = m_dobj;
	substack[subcnt - 1].mprep = m_prep;
	substack[subcnt - 1].miobj = m_iobj;
}

/* Return false if something goes wrong-- such as stack underflow. */
/* This is called *before* pop_subcall */
static rbool pop_subcall_grammar(integer *m_actor, int *vcode,
								 integer *m_dobj, word *m_prep, integer *m_iobj,
								 int *cnum) {
	if (subcnt == 0) return 0;
	vb = substack[subcnt - 1].vb;
	prep = substack[subcnt - 1].prep;
	*cnum = substack[subcnt - 1].cnum;
	*m_actor = substack[subcnt - 1].mactor;
	*vcode = substack[subcnt - 1].vcode;
	*m_dobj = substack[subcnt - 1].mdobj;
	*m_prep = substack[subcnt - 1].mprep;
	*m_iobj = substack[subcnt - 1].miobj;
	return 1;
}




/* ------------------------------------------------------------------- */
/*  Run Metacommand                                                    */
/* ------------------------------------------------------------------- */

static int run_metacommand(int cnum, int *redir_offset)
/* cnum=command number to run. */
/* *redir_offset=offset of redirect header, if we exit with redirection. */
/* 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 occurred
	  4 indicates a subcall has just occurred.
	  5 Is used to go on to the next metacommand after a Return.
	  -2 means we're doing disambiguation and just hit an action token. */
{
	int ip_, oip;  /* ip_=Instruction pointer, oip=Old instruction pointer */
	int r;        /* Used to hold return value from token execution */
	int fail_addr;  /* What address to jump to on failure */
	rbool fail;    /* Last token was a conditional token that failed */
	rbool ortrue, blocktrue, orflag; /* OR stuff
					 orflag: Are we in an OR group?
					 ortrue: Is current OR group true?
					 blocktrue: Is current block w/in OR true?
					 */
	static rbool restart = 0; /* Restarting after subroutine?  */
	op_rec currop;          /* Information on the current token and its args */

	fail_addr = 32000; /* Fall off the end when we fail */
	fail = 0;
	ip_ = 0;
	orflag = blocktrue = ortrue = 0;
	*redir_offset = 1;  /* Default: This is what RedirectTo does.
			   Only XRedirect can send a different value */


	if (restart)  /* finish up Return from subroutine */
		pop_subcall(&cnum, &ip_, &fail_addr);

	if (DEBUG_AGT_CMD && !suppress_debug) {
		debug_head(cnum);
		if (restart) debugout("   (Resuming after subroutine)\n");
	}

	restart = 0;


	/* ==========  Main Loop ================= */
	while (ip_ < command[cnum].cmdsize) {

		oip = ip_;
		ip_ += decode_instr(&currop, command[cnum].data + ip_, command[cnum].cmdsize - ip_);

		/* -------  OR Logic --------------- */
		if (currop.op == 109) { /* OR */
			if (!orflag) { /* First OR; set things up */
				orflag = 1;
				ortrue = 0;
				blocktrue = 1;
			}
			blocktrue = blocktrue && !fail; /* Was the previous token true? */
			fail = 0;
			ortrue = ortrue || blocktrue; /* OR in last block */
			blocktrue = 1; /* New block starts out true. */
		} else if (orflag) { /* we're in the middle of a block */
			blocktrue = blocktrue && !fail; /* Add in previous token */
			fail = 0;
			if (currop.endor) {  /* i.e. not a conditional token */
				orflag = 0;                /* End of OR block */
				ortrue = ortrue || blocktrue; /* OR in last block */
				fail = !ortrue; /* Success of whole group */
			}
		}

		/* ------------  FAILMESSAGE handling ------------- */
		if (currop.failmsg) {  /* Is the current token a Fail... token? */
			if (!fail) continue;  /* Skip it; look at next instruction */
			/* ErrMessage and ErrStdMessage: set disambiguation score */
			if (do_disambig) {
				if (currop.op == 1130 || currop.op == 1131) {
					if (!decode_args(oip, &currop)) return 2;
					disambig_score = currop.arg1;
					return 2;
				} else return -2; /* FailMessage counts as an action token */
			}
			/* Then run the failmessage, skipping the following step... */
		}
		/* -------- Failure routines -------------------- */
		else if (fail) {  /* ... and not failmessage */
			/* consequences of failure */
			fail = 0; /* In case fail_addr doesn't point off the edge of the world */
			ip_ = fail_addr;
			fail_addr = 32000; /* Reset fail_addr */
			continue; /* Usually fail_addr will fall off the end, causing this to
		   return 0 */
		}

		/* - Finish decoding arguments and print out debugging message - */
		if (!decode_args(oip, &currop)) {
			if (currop.op < 1000) fail = currop.negate ? 0 : 1;
			continue;
			/* return 2;*/
		}

		/* -------- Commands that need to be handled specially -------------- */
		if (currop.op == 109) { /* OR */
			if (DEBUG_AGT_CMD && !suppress_debug) debug_newline(op, 0);
			continue; /* OR: skip further processing */
		}

		if (currop.op == 1037) { /* DoSubroutine */
			if (!push_subcall(cnum, ip_, fail_addr)) {
				writeln("GAME ERROR: Subroutine stack overflow.");
				return 2;
			}
			subcall_arg = currop.arg1;
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("--> Call\n");
			return 4;
		}

		if (currop.op == 1038) { /* Return */
			restart = 1;
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("--> Return\n");
			return 5;
		}

		if (currop.op == 1149) { /* Goto */
			ip_ = currop.arg1;
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("\n");
			continue;
		}

		if (currop.op == 1150) { /* OnFailGoto */
			fail_addr = currop.arg1;
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("\n");
			continue;
		}

		if (currop.op == 1152) /* XRedirect */
			*redir_offset = currop.arg1;

		/* ---------- Disambiguation Success -------------- */
		if (do_disambig && currop.disambig) {
			if (DEBUG_AGT_CMD && !suppress_debug) debugout("==> ACTION\n");
			return -2;
		}

		/* ---------- Run normal metacommands -------------- */
		switch (r = exec_instr(&currop)) {
		case 0:  /* Normal action token or successful conditional token */
			if (DEBUG_AGT_CMD && !suppress_debug) debug_newline(op, 0);
			continue;
		case 1: /* Conditional token: fail */
			if (DEBUG_AGT_CMD && !suppress_debug) {
				if (orflag) debugout("  (-->FAIL)\n");
				else debugout("--->FAIL\n");
			}
			fail = 1;
			continue;
		default: /* Return explicit value */
			if (DEBUG_AGT_CMD && !suppress_debug) {
				if (r == 103) debugout("-->Redirect\n");
				else debugout("==> END\n");
			}
			return r - 100;
		}
	}
	return 0;
}



/* ====================================================================*/
/*  SCAN METACOMMAND: These are the routines that scan through the  */
/*    metacommand headers and find the appropriate ones to execute */
/*    Redirection is also handled at this level  */
/* ====================================================================*/


/* ------------------------------------------------------------------- */
/*  Support routines for extracting object information */
/* ------------------------------------------------------------------- */

/* For $ strings. Returns object number if there is one, or negative
   the dictionary index.
   This is used by the metacommand redirection routines */

static integer expand_redirect(word w) {
	assert(w != -1); /* <*NONE*> object shouldn't make it this far */
	if (w == 0 || aver < AGT15) return -w;
	if (w == ext_code[wdverb]) return -syntbl[auxsyn[vb]];
	if (w == ext_code[wdnoun]) return dobj;
	if (w == ext_code[wdobject]) return iobj;
	if (w == ext_code[wdname]) return actor;
	if (w == ext_code[wdadjective]) return -it_adj(dobj);
	if (w == ext_code[wdprep]) return -prep;
	return -w;
}


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

/* Basically, we need to find an object with a matching noun
   and adj to our choice. */
static int extract_obj(word name, word adj) {
	int i, obj;

	/* We just take the first one. We split this into separate noun and
	 creature loops for performance reaons */

	if (name == -1) /* <*NONE*> */
		return 0;

	obj = expand_redirect(name);
	adj = it_name(expand_redirect(adj));

	if (obj > 0) { /* $noun$, $object$, or $name$ */
		if (adj == 0 || adj == it_adj(obj))
			return obj; /* We're done */
		name = it_name(obj);
	} else
		name = -obj;

	if (adj == 0) return -name; /* Adjectives required for CLASS redirect */
	nounloop(i)
	if (noun[i].name == name && noun[i].adj == adj) return i + first_noun;
	creatloop(i)
	if (creature[i].name == name && creature[i].adj == adj)
		return i + first_creat;
	/* Hmm... just hope it's an internal noun. */
	writeln("GAME ERROR: Redirect statement with bad object name.");
	return -name;
}


/* ------------------------------------------------------------------- */
/*  Redirection Routines     */
/* ------------------------------------------------------------------- */


#define wordcode_fix(w) it_name(expand_redirect(w));

/* 'real_obj' below is the dobj_obj/iobj_obj field; it takes
   precedence over anything else if it is nonzero.
   It represents an *explicitly* declared object in
   the header */

static void fix_objnum(integer *objnum, word match,
					   int real_obj,
					   int actor_, int dobj_, int iobj_) {
	if (real_obj) *objnum = real_obj;
	else if (match == ext_code[wdobject]) *objnum = iobj_;
	else if (match == ext_code[wdnoun]) *objnum = dobj_;
	else if (match == ext_code[wdname]) *objnum = actor_;
}

/* Returns TRUE if we changed *objrec, FALSE otherwise */
/*  (This is needed for memory allocation purposes) */
static rbool fix_objrec(parse_rec **objrec, word match,
						int real_obj,
						parse_rec *actrec, parse_rec *dobjrec,
						parse_rec *iobjrec) {
	if (real_obj) *objrec = make_parserec(real_obj, nullptr);
	else if (match == ext_code[wdobject]) *objrec = copy_parserec(iobjrec);
	else if (match == ext_code[wdnoun]) *objrec = copy_parserec(dobjrec);
	else if (match == ext_code[wdname]) *objrec = copy_parserec(actrec);
	else return 0; /* *objrec unchanged */

	return 1;  /* *objrec changed */
}

static void objcode_fix(cmd_rec *cmd)
/* For $ strings. Fixes object redirection if necessary */
{
	int actorword;
	word nounword, objword;
	int dobj_obj, iobj_obj;
	int savedobj, saveactor;
	parse_rec *savedrec, *saveactrec, *saveirec;
	rbool achange, dchange, ichange; /* Did the given _rec ptr change? */

	/* dobj_obj/iobj_obj take precedence over anything else */
	actorword = cmd->actor;
	nounword = cmd->nouncmd;
	objword = cmd->objcmd;
	dobj_obj = cmd->noun_obj;
	iobj_obj = cmd->obj_obj;

	/* Make temporary copies of things for when more than one thing is
	   being shuffled around; we don't need to save iobj since
	   it's processed last */
	saveactor = actor;
	saveactrec = actor_rec;
	savedobj = dobj;
	savedrec = dobj_rec;
	saveirec = iobj_rec; /* Saved only so it can be freed */

	/* Fix object numbers... */
	fix_objnum(&actor, actorword, 0, saveactor, savedobj, iobj);
	fix_objnum(&dobj, nounword, dobj_obj, saveactor, savedobj, iobj);
	fix_objnum(&iobj, objword, iobj_obj, saveactor, savedobj, iobj);

	/* ... and records */
	achange = fix_objrec(&actor_rec, actorword, 0, saveactrec, savedrec, iobj_rec);
	dchange = fix_objrec(&dobj_rec, nounword, dobj_obj, saveactrec, savedrec, iobj_rec);
	ichange = fix_objrec(&iobj_rec, objword, iobj_obj, saveactrec, savedrec, iobj_rec);

	/* Free up whatever needs freeing */
	if (achange) rfree(saveactrec);
	if (dchange) rfree(savedrec);
	if (ichange) rfree(saveirec);
}


/* 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 */

void redirect_exec(cmd_rec *cmd, word *m_actor, int *vcode,
				   word *m_dobj, word *m_prep, word *m_iobj) {
	*m_actor = extract_actor(cmd->actor);
	vb = *vcode = verb_code(it_name(expand_redirect(cmd->verbcmd)));
	*m_dobj = extract_obj(cmd->nouncmd, cmd->noun_adj);
	if (cmd->prep == -1)
		*m_prep = 0;
	else
		*m_prep = it_name(expand_redirect(cmd->prep));
	*m_iobj = extract_obj(cmd->objcmd, cmd->obj_adj);

	/* This shuffles the _real_ objects if $noun$ forms are being
	   used */
	objcode_fix(cmd);
}




/* ------------------------------------------------------------------- */
/*  Scan Metacommand and the matching function it uses                 */
/* ------------------------------------------------------------------- */

/* This is used to match the elements of metacommand trigger patterns */
/* Sees if w2 matches COMMMAND pattern word w1; w1==0 corresponds to ANY */
#define cmatch(w1,w2) ((w1)==0 || (w1)==(w2) || ((w1)==-1 && (w2)==0))

static int cm_actor(int actnum, int actor_)
/* cmd: actnum,  player entry: actor_ */
{
	if (aver < AGX00) return 1; /* Bit of AGT brain-deadness. */
	if (actnum == 1) return actor_ == 0; /* No actor_: just the player  */
	if (tcreat(actnum))
		return (creat_fix[actor_ - first_creat] == creat_fix[actnum - first_creat]);
	if (actnum == 2) return (actor_ != 0); /* ANYBODY? */
	return (actor_ == 0);
}


/* Check that the explicit object matches */
static rbool cm_x_obj(int x_obj, int real_obj) {
	if (x_obj == 0) return 1; /* No explicit object; automatically match. */
	/* Explicit object case */
	/* In this case, we match against the _real_ object */
	/* However, we also require a "normal" match */
	do {
		if (x_obj == real_obj) return 1;
		real_obj = it_class(real_obj);
	} while (real_obj != 0);
	return 0;
}

/* Does [obj] match <adj> <noun> [x_obj]? */
/*  --[obj] must match up with <adj> <noun> */
/*  --If x_obj(the explicit object) is defined, it must match with
	  the "real" object-- that is, the global dobj or iobj value. */
static rbool cm_obj(word name, word adj, int x_obj, int obj, int real_obj) {
	if (name == -1) return (obj == 0); /* <NONE> */

	if (x_obj && !cm_x_obj(x_obj, real_obj)) return 0;

	/* (Note that ANY does not match ALL) */
	if (obj == -ext_code[wall])
		return (name == ext_code[wall] && adj == 0);

	do {  /* Work our way up the class hierarchy */
		if (cmatch(name, it_name(obj)) && cmatch(adj, it_adj(obj)))
			return 1;
		obj = it_class(obj);
	} while (obj != 0);

	return 0;
}



static void scan_dbg(int vcode) {
	char buff[220];
	word w;

	if (vcode >= BASE_VERB && vcode < BASE_VERB + DUMB_VERB
	        && syntbl[synlist[vcode]] != 0)
		w = syntbl[synlist[vcode]];
	else w = syntbl[auxsyn[vcode]];

	if (strlen(dict[w]) > 200) return; /* Just in case... */
	Common::sprintf_s(buff, "+++++Scanning %s\n", dict[w]);
	debugout(buff);
}

#define not_any(n,a) (n!=0 || a!=0)

/* This returns true if we redirect from VERB OBJ {PREP OBJ}
   to something that has fewer objects or no (explicit) preposition.
   This is less perfect than I would like since there is currently
   no way of distinguishing between ANY and an empty slot unless
   the new "NOMATCH" extension is used. */

static rbool redir_narrows_grammar(cmd_rec *cmd1, cmd_rec *cmd2) {
	/* Check inward from obj to prep to noun; if in any of these
	   fields cmd2 has ANY and cmd1 doesn't, return 1.
	   Stop as soon as we find a non-ANY field in either one. */

	/* If we *are* using the new extension, we can just use that info */
	if (cmd2->objcmd == -1) {
		if (cmd1->objcmd != -1) return 1;
		if (cmd1->prep == -1) {
			if (cmd1->prep != -1) return 1;
			if (cmd2->nouncmd == -1 && cmd1->objcmd != -1) return 1;
		}
	}
	if (nomatch_aware) return 0; /* If we are using nomatch, don't need
				  to go through the rest of this nonsense. */

	if (not_any(cmd2->objcmd, cmd2->obj_adj)) return 0;
	if (not_any(cmd1->objcmd, cmd1->obj_adj)) return 1;

	if (cmd2->prep != 0) return 0;
	if (cmd1->prep != 0) return 1;

	if (not_any(cmd2->nouncmd, cmd2->noun_adj)) return 0;
	if (not_any(cmd1->nouncmd, cmd1->noun_adj)) return 1;

	return 0; /* They are both all ANY. */
}



static rbool cm_command(cmd_rec *cmd,
						integer m_actor, int m_verb,
						integer m_dobj, word m_prep, integer m_iobj) {
	if (cmd->verbcmd == 0) { /* ANY */
		if (cmd->actor == 0 && aver >= AGX00)
			return (m_verb == 0); /* ANY command: rest of line ignored */
		/* Else ANY matchs; go on to test other things. */
	} else if (cmd->verbcmd != m_verb) return 0;

	return
	    cm_actor(cmd->actor, m_actor)
	    && cm_obj(cmd->nouncmd, cmd->noun_adj, cmd->noun_obj, m_dobj, dobj)
	    && cmatch(cmd->prep, m_prep)
	    && cm_obj(cmd->objcmd, cmd->obj_adj, cmd->obj_obj, m_iobj, iobj);
}



static void scan_for_actor(integer m_actor, int *start, int *end) {
	int i;

	assert(m_actor != 0);

	if (aver >= AGX00) {
		if (start != nullptr) *start = verbptr[DIR_ADDR_CODE];
		*end = verbend[DIR_ADDR_CODE];
		return;
	}
	for (i = verbend[DIR_ADDR_CODE]; i > verbptr[DIR_ADDR_CODE]; i--)
		if (creat_fix[command[i].actor - first_creat]
		        == creat_fix[m_actor - first_creat]) {
			i++;
			break;
		}
	*end = i;

	if (start == nullptr) return;

	for (i = verbptr[DIR_ADDR_CODE]; i <= *end; i++)
		if (creat_fix[command[i].actor - first_creat]
		        == creat_fix[m_actor - first_creat])
			break;
	*start = i;
}


/* m_<word> are the matching criterion; they have no *necessary*
  connection to dobj, iobj, etc. */

int scan_metacommand(integer m_actor, int vcode,
					 integer m_dobj, word m_prep, integer m_iobj,
					 int *redir_flag)
/* 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. */
/* If redir_flag is non-NULL, it is set when redirection occurs:
   1+=Redirection occurred
   2=Grammar-changing redirection occurred. */
{
	int i, oldi;
	word m_verb;
	int scanend;
	int redir_offset;   /* Used for multiple redirects in the same
			 metacommand (which can occur in AGATE-style
			 commands)-- this is used to hold the offset
			 of the given redirect. */
	long redirect_count;  /* This is a safety measure: this keeps track of how
			many redirections have occurred 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. */

	rfree(substack);
	subcnt = 0;
	subsize = 0;
	redirect_count = 0;

	if (mars_fix)
		if (vcode == 0 || m_actor == 2) return 0;
	/* Don't explicity scan ANY metacommands if MARS fix is active. */
	if (m_actor == -ext_code[weverybody]) m_actor = 2;


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

	m_verb = syntbl[auxsyn[vcode]];
	if (m_actor == 0) {
		i = verbptr[vcode];
		scanend = verbend[vcode];
	} else
		scan_for_actor(m_actor, &i, &scanend);
	for (; i < scanend; i++)
		if (command[i].actor < 0) {
			/* REDIRECT data; skip over it */;
		} else if (cm_command(&command[i], m_actor, m_verb, m_dobj, m_prep, m_iobj))
			switch (run_metacommand(i, &redir_offset)) {
			case -2:
				rfree(substack);
				return -2;
			/* We are doing disambiguation and reached
			   an action token */
			case 0:
			default:
				break; /* Go onto next metacommand */
			case 1:
				rfree(substack);
				return 1;  /* Done with metacommands */
			case 2:
				rfree(substack);
				return 2;  /* Done with turn */


			/* -------- REDIRECTION  ------------ */
			/* This handles RedirectTo tokens */
			case 3:
				oldi = i;
				i += redir_offset;
				if (i == last_cmd || command[i].actor > 0) {
					if (!PURE_ERROR) writeln("GAME ERROR: Invalid REDIRECT token.");
					rfree(substack);
					return 2;
				}
				if (MAX_REDIR != 0 && ++redirect_count > MAX_REDIR) {
					if (!PURE_ERROR) writeln("GAME ERROR: Infinite REDIRECT loop.");
					rfree(substack);
					return 2;
				}
				if (DEBUG_AGT_CMD && !suppress_debug) {
					debugout("   ==>");
					debug_head(i);
				}

				/* REDIRECT :If we do a redirect from a broader grammar to a
				   narrower grammar, it will be noted so that certain types
				   of grammar checking can be disabled. */
				if (redir_flag != nullptr) {
					if (*redir_flag < 2
					        && redir_narrows_grammar(&command[oldi], &command[i]))
						*redir_flag = 2;

					/* Set *redir_flag to at least 1 if we do *any* redirection. */
					if (!*redir_flag) *redir_flag = 1;
				}

				/* REDIRECT: Do the actual redirection, building the new command
				   header and shuffling around nouns and verbs as
				   necessary */
				redirect_exec(&command[i], &m_actor, &vcode,
				              &m_dobj, &m_prep, &m_iobj);

				/* REDIRECT: Start scanning again from the beginning */
				if (!mars_fix) {/* In MARS, we *don't* go back to the top */
					if (m_actor != 0)
						scan_for_actor(m_actor, &i, &scanend);
					else {
						i = verbptr[vcode];
						scanend = verbend[vcode];
					}
					i--; /* Back up one so that the following i++ we'll
			be at the right location */
				}

				/* 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. */
				m_verb = syntbl[auxsyn[vcode]];
				break;



			/* -------- SUBROUTINE CALL  ------------ */
			case 4:  /* Subroutine Call -- same idea as RedirectTo,
		  but less complicated */
				push_subcall_grammar(m_actor, vcode, m_dobj, m_prep, m_iobj, i);
				vcode = verb_code(sub_name[subcall_arg - 1]);
				m_actor = m_dobj = m_iobj = 0;
				m_prep = 0;

				if (!mars_fix) /* In MARS, we *don't* go back to the top */
					i = verbptr[vcode] - 1;
				scanend = verbend[vcode];
				m_verb = syntbl[auxsyn[vcode]];
				break;


			/* -------- RETURN  ------------ */
			case 5: /* Return: pop grammar state, then ... ? */
				if (!pop_subcall_grammar(&m_actor, &vcode,
				                         &m_dobj, &m_prep, &m_iobj, &i)) {
					writeln("GAME ERROR: Return without DoSubroutine.");
					rfree(substack);
					return 2;
				}

				if (m_actor == 0)
					scanend = verbend[vcode];
				else
					scan_for_actor(m_actor, nullptr, &scanend);
				m_verb = syntbl[auxsyn[vcode]];

				i--; /* Cause the last command to restart,
		  at which point run_command will pop the rest of the
		  stack. */

				break;
			}
	rfree(substack);
	return 0; /* Done with this cycle of metacommands */
}

/* ====================================================================*/

#undef cm

} // End of namespace AGT
} // End of namespace Glk
