/* 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "glk/alan2/alan2.h"
#include "glk/alan2/types.h"
#include "glk/alan2/exe.h"
#include "glk/alan2/glkio.h"
#include "glk/alan2/inter.h"
#include "glk/alan2/main.h"
#include "glk/alan2/parse.h"
#include "glk/alan2/stack.h"
#include "glk/alan2/decode.h"

namespace Glk {
namespace Alan2 {

#define WIDTH 80

#define N_EVTS 100


/* PUBLIC DATA */

/* The event queue */
EvtqElem eventq[N_EVTS];        /* Event queue */
int etop = 0;                   /* Event queue top pointer */

Boolean looking = FALSE;        /* LOOKING? flag */

int dscrstkp = 0;               /* Describe-stack pointer */


void dscrobjs();
void dscracts();


void print(Aword fpos, Aword len) {
	char str[2 * WIDTH];              // String buffer
	int outlen = 0;                   // Current output length
	int ch = 0;
	int i;
	long savfp = 0;                   // Temporary saved text file position
	static Boolean printFlag = FALSE; // Printing already?
	Boolean savedPrintFlag = printFlag;
	void *info = nullptr;             // Saved decoding info


	if (len == 0) return;

	if (isHere(HERO)) {           /* Check if the player will see it */
		if (printFlag) {            /* Already printing? */
			/* Save current text file position and/or decoding info */
			if (header->pack)
				info = pushDecode();
			else
				savfp = ftell(txtfil);
		}
		printFlag = TRUE;           /* We're printing now! */
		fseek(txtfil, fpos, 0);     /* Position to start of text */
		if (header->pack)
			startDecoding();
		for (outlen = 0; outlen != (int)len; outlen = outlen + strlen(str)) {
			/* Fill the buffer from the beginning */
			for (i = 0; i <= WIDTH || (i > WIDTH && ch != ' '); i++) {
				if (outlen + i == (int)len)  /* No more characters? */
					break;
				if (header->pack)
					ch = decodeChar();
				else
					ch = getc(txtfil);
				if (ch == EOFChar)      /* Or end of text? */
					break;
				str[i] = ch;
			}
			str[i] = '\0';
			output(str);
		}
		/* And restore */
		printFlag = savedPrintFlag;
		if (printFlag) {
			if (header->pack)
				popDecode(info);
			else
				fseek(txtfil, savfp, 0);
		}
	}
}

void sys(Aword fpos, Aword len) {
#ifdef GLK
	::error("system calls aren't supported");
#else
	char *command;

	getstr(fpos, len);            /* Returns address to string on stack */
	command = (char *)pop();
	int tmp = system(command);
	free(command);
#endif
}

void getstr(Aword fpos, Aword len) {
	char *buf = (char *)allocate(len + 1);

	push((Aptr) buf);            /* Push the address to the string */
	fseek(txtfil, fpos, 0);       /* Position to start of text */
	if (header->pack)
		startDecoding();
	while (len--)
		if (header->pack)
			*(buf++) = decodeChar();
		else
			*(buf++) = getc(txtfil);
	*buf = '\0';
}

void score(Aword sc) {
	char buf[80];

	if (sc == 0) {
		prmsg(M_SCORE1);
		sprintf(buf, "%d", cur.score);
		output(buf);
		prmsg(M_SCORE2);
		sprintf(buf, "%ld.", (unsigned long) header->maxscore);
		output(buf);
	} else {
		cur.score += scores[sc - 1];
		scores[sc - 1] = 0;
	}
}

void visits(Aword v) {
	cur.visits = v;
}

Boolean confirm(MsgKind msgno) {
	char buf[80];

	/* This is a bit of a hack since we really want to compare the input,
	   it could be affirmative, but for now any input is NOT! */
	prmsg(msgno);

	if (!readline(buf)) return TRUE;
	col = 1;

	return (buf[0] == '\0');
}

void quit(CONTEXT) {
	char buf[80];

	para();
	while (!g_vm->shouldQuit()) {
		col = 1;
		statusline();
		prmsg(M_QUITACTION);
		if (!readline(buf)) {
			CALL1(terminate, 0)
		}

		if (scumm_stricmp(buf, "restart") == 0) {
			g_vm->setRestart(true);
			LONG_JUMP
		} else if (scumm_stricmp(buf, "restore") == 0) {
			restore();
			LONG_JUMP
		} else if (scumm_stricmp(buf, "quit") == 0) {
			CALL1(terminate, 0)
		}
	}
}

void restart() {
	para();
	if (confirm(M_REALLY)) {
		//longjmp(restart_label, TRUE);
		::error("TODO: restart");
	} else
		return;
	syserr("Fallthrough in RESTART");
}

void cancl(Aword evt) {
	int i;

	for (i = etop - 1; i >= 0; i--)
		if (eventq[i].event == (int)evt) {
			while (i < etop - 1) {
				eventq[i].event = eventq[i + 1].event;
				eventq[i].time = eventq[i + 1].time;
				eventq[i].where = eventq[i + 1].where;
				i++;
			}
			etop--;
			return;
		}
}

void schedule(Aword evt, Aword whr, Aword aft) {
	int i;
	int time;

	cancl(evt);
	/* Check for overflow */
	if (etop == N_EVTS) syserr("Out of event space.");

	time = cur.tick + aft;

	/* Bubble this event down */
	for (i = etop; i >= 1 && eventq[i - 1].time <= time; i--) {
		eventq[i].event = eventq[i - 1].event;
		eventq[i].time = eventq[i - 1].time;
		eventq[i].where = eventq[i - 1].where;
	}

	eventq[i].time = time;
	eventq[i].where = whr;
	eventq[i].event = evt;
	etop++;
}


/*----------------------------------------------------------------------

  getatr()

  Get an attribute value from an attribute list

 */
static Aptr getatr(
    Aaddr atradr,              /* IN - ACODE address to attribute table */
    Aaddr atr                  /* IN - The attribute to read */
) {
	AtrElem *at;

	at = (AtrElem *) addrTo(atradr);
	return at[atr - 1].val;
}


/*----------------------------------------------------------------------

  setatr()

  Set a particular attribute to a value.

 */
static void setatr(
	Aaddr atradr,              /* IN - ACODE address to attribute table */
	Aword atr,                 /* IN - attribute code */
	Aword val                  /* IN - new value */
) {
	AtrElem *at;

	at = (AtrElem *) addrTo(atradr);
	at[atr - 1].val = val;
}


/*----------------------------------------------------------------------

  make()

  */

static void makloc(Aword loc, Aword atr, Aword val) {
	setatr(locs[loc - LOCMIN].atrs, atr, val);
}

static void makobj(Aword obj, Aword atr, Aword val) {
	setatr(objs[obj - OBJMIN].atrs, atr, val);
}

static void makact(Aword act, Aword atr, Aword val) {
	setatr(acts[act - ACTMIN].atrs, atr, val);
}

void make(Aword id, Aword atr, Aword val) {
	char str[80];

	if (isObj(id))
		makobj(id, atr, val);
	else if (isLoc(id))
		makloc(id, atr, val);
	else if (isAct(id))
		makact(id, atr, val);
	else {
		sprintf(str, "Can't MAKE item (%ld).", (unsigned long) id);
		syserr(str);
	}
}


/*----------------------------------------------------------------------------

  set()

 */

static void setloc(Aword loc, Aword atr, Aword val) {
	setatr(locs[loc - LOCMIN].atrs, atr, val);
	locs[loc - LOCMIN].describe = 0;
}

static void setobj(Aword obj, Aword atr, Aword val) {
	setatr(objs[obj - OBJMIN].atrs, atr, val);
}

static void setact(Aword act, Aword atr, Aword val) {
	setatr(acts[act - ACTMIN].atrs, atr, val);
}

void set(Aword id, Aword atr, Aword val) {
	char str[80];

	if (isObj(id))
		setobj(id, atr, val);
	else if (isLoc(id))
		setloc(id, atr, val);
	else if (isAct(id))
		setact(id, atr, val);
	else {
		sprintf(str, "Can't SET item (%ld).", (unsigned long) id);
		syserr(str);
	}
}

void setstr(Aword id, Aword atr, Aword str) {
	free((char *)attribute(id, atr));
	set(id, atr, str);
}



/*-----------------------------------------------------------------------------

  incr/decr

  */

/*----------------------------------------------------------------------

  incratr()

  Increment a particular attribute by a value.

 */
static void incratr(
    Aaddr atradr,           /* IN - ACODE address to attribute table */
    Aword atr,              /* IN - attribute code */
    Aword step              /* IN - step to increment by */
) {
	AtrElem *at;

	at = (AtrElem *) addrTo(atradr);
	at[atr - 1].val += step;
}

static void incrloc(Aword loc, Aword atr, Aword step) {
	incratr(locs[loc - LOCMIN].atrs, atr, step);
	locs[loc - LOCMIN].describe = 0;
}

static void incrobj(Aword obj, Aword atr, Aword step) {
	incratr(objs[obj - OBJMIN].atrs, atr, step);
}

static void incract(Aword act, Aword atr, Aword step) {
	incratr(acts[act - ACTMIN].atrs, atr, step);
}

void incr(Aword id, Aword atr, Aword step) {
	char str[80];

	if (isObj(id))
		incrobj(id, atr, step);
	else if (isLoc(id))
		incrloc(id, atr, step);
	else if (isAct(id))
		incract(id, atr, step);
	else {
		sprintf(str, "Can't INCR item (%ld).", (unsigned long) id);
		syserr(str);
	}
}

void decr(Aword id, Aword atr, Aword step) {
	char str[80];

	// TODO: Original did explicit negation on an unsigned value. Make sure that the
	// casts added to ignore the warnings are okay
	if (isObj(id))
		incrobj(id, atr, static_cast<uint>(-(int)step));
	else if (isLoc(id))
		incrloc(id, atr, static_cast<uint>(-(int)step));
	else if (isAct(id))
		incract(id, atr, static_cast<uint>(-(int)step));
	else {
		sprintf(str, "Can't DECR item (%ld).", (unsigned long) id);
		syserr(str);
	}
}


/*----------------------------------------------------------------------

  attribute()

  */

static Aptr locatr(Aword loc, Aword atr) {
	return getatr(locs[loc - LOCMIN].atrs, atr);
}

static Aptr objatr(Aword obj, Aword atr) {
	return getatr(objs[obj - OBJMIN].atrs, atr);
}

static Aptr actatr(Aword act, Aword atr) {
	return getatr(acts[act - ACTMIN].atrs, atr);
}

static Aptr litatr(Aword lit, Aword atr) {
	char str[80];

	if (atr == 1)
		return litValues[lit - LITMIN].value;
	else {
		sprintf(str, "Unknown attribute for literal (%ld).", (unsigned long) atr);
		syserr(str);
	}
	return (Aptr)EOD;
}

Aptr attribute(Aword id, Aword atr) {
	char str[80];

	if (isObj(id))
		return objatr(id, atr);
	else if (isLoc(id))
		return locatr(id, atr);
	else if (isAct(id))
		return actatr(id, atr);
	else if (isLit(id))
		return litatr(id, atr);
	else {
		sprintf(str, "Can't ATTRIBUTE item (%ld).", (unsigned long) id);
		syserr(str);
	}
	return (Aptr)EOD;
}

Aptr strattr(Aword id, Aword atr) {
	return (Aptr) strdup((char *)attribute(id, atr));
}


/*----------------------------------------------------------------------

  where()

  */

static Aword objloc(Aword obj) {
	if (isCnt(objs[obj - OBJMIN].loc)) /* In something ? */
		if (isObj(objs[obj - OBJMIN].loc) || isAct(objs[obj - OBJMIN].loc))
			return (where(objs[obj - OBJMIN].loc));
		else /* Containers not anywhere is where the hero is! */
			return (where(HERO));
	else
		return (objs[obj - OBJMIN].loc);
}

static Aword actloc(Aword act) {
	return (acts[act - ACTMIN].loc);
}

Aword where(Aword id) {
	char str[80];

	if (isObj(id))
		return objloc(id);
	else if (isAct(id))
		return actloc(id);
	else {
		sprintf(str, "Can't WHERE item (%ld).", (unsigned long) id);
		syserr(str);
	}
	return (Aptr)EOD;
}


/*----------------------------------------------------------------------

  aggregates

  */

Aint agrmax(Aword atr, Aword whr) {
	Aword i;
	Aint max = 0;

	for (i = OBJMIN; i <= OBJMAX; i++) {
		if (isLoc(whr)) {
			if (where(i) == whr && (int)attribute(i, atr) > max)
				max = attribute(i, atr);
		} else if (objs[i - OBJMIN].loc == whr && (int)attribute(i, atr) > max)
			max = attribute(i, atr);
	}
	return (max);
}

Aint agrsum(Aword atr, Aword whr) {
	Aword i;
	Aint sum = 0;

	for (i = OBJMIN; i <= OBJMAX; i++) {
		if (isLoc(whr)) {
			if (where(i) == whr)
				sum += attribute(i, atr);
		} else if (objs[i - OBJMIN].loc == whr)
			sum += attribute(i, atr);
	}
	return (sum);
}

Aint agrcount(Aword whr) {
	Aword i;
	Aword count = 0;

	for (i = OBJMIN; i <= OBJMAX; i++) {
		if (isLoc(whr)) {
			if (where(i) == whr)
				count++;
		} else if (objs[i - OBJMIN].loc == whr)
			count++;
	}
	return (count);
}


/*----------------------------------------------------------------------

  locate()

  */

static void locobj(Aword obj, Aword whr) {
	if (isCnt(whr)) { /* Into a container */
		if (whr == obj)
			syserr("Locating something inside itself.");
		if (checklim(whr, obj))
			return;
		else
			objs[obj - OBJMIN].loc = whr;
	} else {
		objs[obj - OBJMIN].loc = whr;
		/* Make sure the location is described since it's changed */
		locs[whr - LOCMIN].describe = 0;
	}
}

static void locact(Aword act, Aword whr) {
	Aword prevact = cur.act;
	Aword prevloc = cur.loc;

	cur.loc = whr;
	acts[act - ACTMIN].loc = whr;
	if (act == HERO) {
		if (locs[acts[act - ACTMIN].loc - LOCMIN].describe % (cur.visits + 1) == 0)
			look();
		else {
			if (anyOutput)
				para();
			say(where(HERO));
			prmsg(M_AGAIN);
			newline();
			dscrobjs();
			dscracts();
		}
		locs[where(HERO) - LOCMIN].describe++;
		locs[where(HERO) - LOCMIN].describe %= (cur.visits + 1);
	} else
		locs[whr - LOCMIN].describe = 0;
	if (locs[cur.loc - LOCMIN].does != 0) {
		cur.act = act;
		interpret(locs[cur.loc - LOCMIN].does);
		cur.act = prevact;
	}

	if (cur.act != (int)act)
		cur.loc = prevloc;
}

void locate(Aword id, Aword whr) {
	char str[80];

	if (isObj(id))
		locobj(id, whr);
	else if (isAct(id))
		locact(id, whr);
	else {
		sprintf(str, "Can't LOCATE item (%ld).", (unsigned long) id);
		syserr(str);
	}
}


/*----------------------------------------------------------------------

  isHere()

  */

static Abool objhere(Aword obj) {
	if (isCnt(objs[obj - OBJMIN].loc)) {  /* In something? */
		if (isObj(objs[obj - OBJMIN].loc) || isAct(objs[obj - OBJMIN].loc))
			return (isHere(objs[obj - OBJMIN].loc));
		else /* If the container wasn't anywhere, assume where HERO is! */
			return ((int)where(HERO) == cur.loc);
	} else {
		return (int)(objs[obj - OBJMIN].loc) == cur.loc;
	}
}

static Aword acthere(Aword act) {
	return (int)(acts[act - ACTMIN].loc) == cur.loc;
}

Abool isHere(Aword id) {
	char str[80];

	if (isObj(id))
		return objhere(id);
	else if (isAct(id))
		return acthere(id);
	else {
		sprintf(str, "Can't HERE item (%ld).", (unsigned long) id);
		syserr(str);
	}
	return (Abool)EOD;
}

/*----------------------------------------------------------------------

  isNear()

  */

static Aword objnear(Aword obj) {
	if (isCnt(objs[obj - OBJMIN].loc)) {  /* In something? */
		if (isObj(objs[obj - OBJMIN].loc) || isAct(objs[obj - OBJMIN].loc))
			return (isNear(objs[obj - OBJMIN].loc));
		else  /* If the container wasn't anywhere, assume here, so not nearby! */
			return (FALSE);
	} else
		return (exitto(where(obj), cur.loc));
}

static Aword actnear(Aword act) {
	return (exitto(where(act), cur.loc));
}

Abool isNear(Aword id) {
	char str[80];

	if (isObj(id))
		return objnear(id);
	else if (isAct(id))
		return actnear(id);
	else {
		sprintf(str, "Can't NEAR item (%ld).", (unsigned long) id);
		syserr(str);
	}
	return (Abool)EOD;
}


/*----------------------------------------------------------------------

  in()

  */

Abool in(Aword obj, Aword cnt) {
	if (!isObj(obj))
		return (FALSE);
	if (!isCnt(cnt))
		syserr("IN in a non-container.");

	return (objs[obj - OBJMIN].loc == cnt);
}


/*----------------------------------------------------------------------

  say()

  */

static void sayloc(Aword loc) {
	interpret(locs[loc - LOCMIN].nams);
}

static void sayobj(Aword obj) {
	interpret(objs[obj - OBJMIN].dscr2);
}

static void sayact(Aword act) {
	interpret(acts[act - ACTMIN].nam);
}

void sayint(Aword val) {
	char buf[25];

	if (isHere(HERO)) {
		sprintf(buf, "%ld", (unsigned long) val);
		output(buf);
	}
}

void saystr(char *str) {
	if (isHere(HERO))
		output(str);
	free(str);
}

static void saylit(Aword lit) {
	char *str;

	if (isNum(lit))
		sayint(litValues[lit - LITMIN].value);
	else {
		str = (char *)strdup((char *)litValues[lit - LITMIN].value);
		saystr(str);
	}
}

void sayarticle(Aword id) {
	if (!isObj(id))
		syserr("Trying to say article of something *not* an object.");
	if (objs[id - OBJMIN].art != 0)
		interpret(objs[id - OBJMIN].art);
	else
		prmsg(M_ARTICLE);
}

void say(Aword id) {
	char str[80];

	if (isHere(HERO)) {
		if (isObj(id))
			sayobj(id);
		else if (isLoc(id))
			sayloc(id);
		else if (isAct(id))
			sayact(id);
		else if (isLit(id))
			saylit(id);
		else {
			sprintf(str, "Can't SAY item (%ld).", (unsigned long) id);
			syserr(str);
		}
	}
}


/*----------------------------------------------------------------------

  describe()

  */

static void dscrloc(Aword loc) {
	if (locs[loc - LOCMIN].dscr != 0)
		interpret(locs[loc - LOCMIN].dscr);
}

static void dscrobj(Aword obj) {
	objs[obj - OBJMIN].describe = FALSE;
	if (objs[obj - OBJMIN].dscr1 != 0)
		interpret(objs[obj - OBJMIN].dscr1);
	else {
		prmsg(M_SEEOBJ1);
		sayarticle(obj);
		say(obj);
		prmsg(M_SEEOBJ4);
		if (objs[obj - OBJMIN].cont != 0)
			list(obj);
	}
}

static void dscract(Aword act) {
	ScrElem *scr = NULL;

	if (acts[act - ACTMIN].script != 0) {
		for (scr = (ScrElem *) addrTo(acts[act - ACTMIN].scradr); !endOfTable(scr); scr++)
			if (scr->code == acts[act - ACTMIN].script)
				break;
		if (endOfTable(scr)) scr = NULL;
	}
	if (scr != NULL && scr->dscr != 0)
		interpret(scr->dscr);
	else if (acts[act - ACTMIN].dscr != 0)
		interpret(acts[act - ACTMIN].dscr);
	else {
		interpret(acts[act - ACTMIN].nam);
		prmsg(M_SEEACT);
	}
	acts[act - ACTMIN].describe = FALSE;
}


static Aword dscrstk[255];

void describe(Aword id) {
	int i;
	char str[80];

	for (i = 0; i < dscrstkp; i++)
		if (dscrstk[i] == id)
			syserr("Recursive DESCRIBE.");
	dscrstk[dscrstkp++] = id;

	if (isObj(id))
		dscrobj(id);
	else if (isLoc(id))
		dscrloc(id);
	else if (isAct(id))
		dscract(id);
	else {
		sprintf(str, "Can't DESCRIBE item (%ld).", (unsigned long) id);
		syserr(str);
	}

	dscrstkp--;
}


/*----------------------------------------------------------------------

  use()

  */

void use(Aword act, Aword scr) {
	char str[80];

	if (!isAct(act)) {
		sprintf(str, "Item is not an Actor (%ld).", (unsigned long) act);
		syserr(str);
	}

	acts[act - ACTMIN].script = scr;
	acts[act - ACTMIN].step = 0;
}


/*----------------------------------------------------------------------

  list()

  */

void list(Aword cnt) {
	uint i;
	Aword props;
	Aword prevobj = 0;
	Boolean found = FALSE;
	Boolean multiple = FALSE;

	/* Find container properties */
	if (isObj(cnt))
		props = objs[cnt - OBJMIN].cont;
	else if (isAct(cnt))
		props = acts[cnt - ACTMIN].cont;
	else
		props = cnt;

	for (i = OBJMIN; i <= OBJMAX; i++) {
		if (in(i, cnt)) { /* Yes, it's in this container */
			if (!found) {
				found = TRUE;
				if (cnts[props - CNTMIN].header != 0)
					interpret(cnts[props - CNTMIN].header);
				else {
					prmsg(M_CONTAINS1);
					if (cnts[props - CNTMIN].nam != 0) /* It has it's own name */
						interpret(cnts[props - CNTMIN].nam);
					else
						say(cnts[props - CNTMIN].parent); /* It is actually an object or actor */
					prmsg(M_CONTAINS2);
				}
			} else {
				if (multiple) {
					needsp = FALSE;
					prmsg(M_CONTAINS3);
				}
				multiple = TRUE;
				sayarticle(prevobj);
				say(prevobj);
			}
			prevobj = i;
		}
	}

	if (found) {
		if (multiple)
			prmsg(M_CONTAINS4);
		sayarticle(prevobj);
		say(prevobj);
		prmsg(M_CONTAINS5);
	} else {
		if (cnts[props - CNTMIN].empty != 0)
			interpret(cnts[props - CNTMIN].empty);
		else {
			prmsg(M_EMPTY1);
			if (cnts[props - CNTMIN].nam != 0) /* It has it's own name */
				interpret(cnts[props - CNTMIN].nam);
			else
				say(cnts[props - CNTMIN].parent); /* It is actually an actor or object */
			prmsg(M_EMPTY2);
		}
	}
	needsp = TRUE;
}


/*----------------------------------------------------------------------

  empty()

  */

void empty(Aword cnt, Aword whr) {
	for (uint i = OBJMIN; i <= OBJMAX; i++)
		if (in(i, cnt))
			locate(i, whr);
}


/*----------------------------------------------------------------------*\

  Description of current location

  dscrobjs()
  dscracts()
  look()

\*----------------------------------------------------------------------*/

void dscrobjs() {
	uint i;
	int prevobj = 0;
	Boolean found = FALSE;
	Boolean multiple = FALSE;

	/* First describe everything here with its own description */
	for (i = OBJMIN; i <= OBJMAX; i++)
		if ((int)objs[i - OBJMIN].loc == cur.loc &&
		        objs[i - OBJMIN].describe &&
		        objs[i - OBJMIN].dscr1)
			describe(i);

	/* Then list everything else here */
	for (i = OBJMIN; i <= OBJMAX; i++)
		if ((int)objs[i - OBJMIN].loc == cur.loc && objs[i - OBJMIN].describe) {
			if (!found) {
				prmsg(M_SEEOBJ1);
				sayarticle(i);
				say(i);
				found = TRUE;
			} else {
				if (multiple) {
					needsp = FALSE;
					prmsg(M_SEEOBJ2);
					sayarticle(prevobj);
					say(prevobj);
				}
				multiple = TRUE;
			}
			prevobj = i;
		}

	if (found) {
		if (multiple) {
			prmsg(M_SEEOBJ3);
			sayarticle(prevobj);
			say(prevobj);
		}
		prmsg(M_SEEOBJ4);
	}

	/* Set describe flag for all objects */
	for (i = OBJMIN; i <= OBJMAX; i++)
		objs[i - OBJMIN].describe = TRUE;
}

void dscracts() {
	uint i;

	for (i = HERO + 1; i <= ACTMAX; i++)
		if ((int)acts[i - ACTMIN].loc == cur.loc && acts[i - ACTMIN].describe)
			describe(i);

	/* Set describe flag for all actors */
	for (i = HERO; i <= ACTMAX; i++)
		acts[i - ACTMIN].describe = TRUE;
}

void look() {
	uint i;

	if (looking)
		syserr("Recursive LOOK.");

	looking = TRUE;
	/* Set describe flag for all objects and actors */
	for (i = OBJMIN; i <= OBJMAX; i++)
		objs[i - OBJMIN].describe = TRUE;
	for (i = ACTMIN; i <= ACTMAX; i++)
		acts[i - ACTMIN].describe = TRUE;

	if (anyOutput)
		para();

	g_vm->glk_set_style(style_Subheader);
	needsp = FALSE;
	say(cur.loc);
	needsp = FALSE;
	output(".");
	g_vm->glk_set_style(style_Normal);
	newline();
	needsp = FALSE;
	describe(cur.loc);
	dscrobjs();
	dscracts();
	looking = FALSE;
}


/*----------------------------------------------------------------------

  save()

  */

void save() {
	(void)g_vm->saveGame();
}


/*----------------------------------------------------------------------

  restore()

  */

void restore() {
	(void)g_vm->loadGame();
}


/*----------------------------------------------------------------------

  rnd()

  */

Aword rnd(Aword from, Aword to) {
	if (to == from)
		return to;
	else if (to > from)
		return (rand() / 10) % (to - from + 1) + from;
	else
		return (rand() / 10) % (from - to + 1) + to;
}

/*----------------------------------------------------------------------

  btw()

  BETWEEN

  */

Abool btw(Aint val, Aint low, Aint high) {
	if (high > low)
		return low <= val && val <= high;
	else
		return high <= val && val <= low;
}



/*----------------------------------------------------------------------

  contains()

  */

Aword contains(Aptr string, Aptr substring) {
	Abool found;

	strlow((char *)string);
	strlow((char *)substring);

	found = (strstr((char *)string, (char *)substring) != 0);

	free((char *)string);
	free((char *)substring);

	return (found);
}


/*----------------------------------------------------------------------

  streq()

  Compare two strings approximately, ignore case

  */
Abool streq(char a[], char b[]) {
	Boolean eq;

	strlow(a);
	strlow(b);

	eq = (strcmp(a, b) == 0);

	free(a);
	free(b);

	return (eq);
}

} // End of namespace Alan2
} // End of namespace Glk