/* TMORPH2.T
 * This library imitates Kevin Forchione's tmorph.t library. However, it fixes
 * a major problem with the original library, which could not handle strings
 * with embedded code (i.e. code in <<>>). This version also handles recursive
 * expressions.
 * 
 * For the copyright notice to the original tmorph.t, see the end of this
 * file. While this file is not simply a modified version of the original (I
 * wrote most of it from scratch), there are a few portions which are almost
 * direct copies. 
 *
 * This library Copyright 2003 by Shadow Wolf. All Rights Reserved. Distribution
 * terms are the same as Kevin Forchione's original library:
 *
 *  	You may modify and use this file in any way you want, provided that
 *		if you redistribute modified copies of this file in source form, the
 *   	copies must include the original copyright notice (including this
 *   	paragraph), and must be clearly marked as modified from the original
 *   	version.
 */

/*******************************************************
 * Using TMORPH2.T
 *
 * Morph expressions are embedded within the text string you want to display,
 * using square brackets '[]' to delimit them. Each morph expression consists of
 * the following elements:
 
 * An optional initializer followed by a pipe '|': The initializer can either
 * be a number (typically generated by an embedded expression) or a name and
 * type pair. If the pipe is used without an initializer, a random value will
 * be used. If no initializer or pipe is used, then the most recently
 * generated value will be used.
 * 
 * After the initializer is a list of alternative strings, separated by
 * slashes '/'. You can leave an element blank in order to not display any
 * text.
 *
 * This improved version of the text morphing library allows for recursive
 * expressions. The innermost morph expression is generated first, with its
 * result placed in the outer expression. Evaluated expressions (code in << >>
 * delimiters) are also evaluated before the morph expression containing them.
 *
 * This version also handles HTML tags (by ignoring them), if USE_HTML_PROMPT is
 * defined. (I figure anyone writing an HTML game is going to use HTML prompts as a
 * minimum.)
 *
 * Example:
 *
 * (From Kevin Forchione's original library)
 *      "[|A ball/Balls] of searing flame burst[s/] 
 *      out of your magic ring, rebound[s/] off of the ground, and 
 *      vaporize[s/] the kni[fe/ves] before [it/they] 
 *      can reach you.";
 * 		
 * The first expression "[|A ball/Balls] has the pipe to initialize the
 * sequence, and the remaining expressions use the same value.
 * 
 * Dynamic Control: Since embedded expressions can be evaluated, simply
 * include the expression before the pipe:
 *
 *		"[<<object.knifecount>>|A ball/Balls/A mighty fireball]"
 * 
 * No need to modify global variables (I'm not using a global anyway :-)
 *
 * Recursive Example:
 *
 *      magicRing: clothingItem
 *          sdesc = "[|gold /magic /plain gold /]ring"
 *          noun = 'ring'
 *          adjective = 'plain' 'gold' 'magic'
 *          putOnDesc = "[ringPutOnDesc rrm|%You% put%s% on
 *          <<self.thedesc>>./<<caps()>><<self.thedesc>> fits neatly on %your%
 *          finger./Worn.] "
 *          location = startroom
 *      ;      
 *
 * Special Sequences:
 *
 * There are five types of sequences, encoded with seq, rnd, mod, rrs, rrm.
 *
 * MorphSequencer (seq)
 * --------------------
 *
 * 		[myseq seq|A ball/Balls/A mighty fireball]
 * 
 * Creates a sequence named 'myseq', which produces 'A ball', 'Balls', and
 * 'A mighty fireball' in that order, continuing with 'A mighty fireball' for
 * all subsequent calls.
 *
 * MorphRandomizer (rnd)
 * ---------------------
 *
 *		[myrnd rnd|A ball/Balls/A mighty fireball]
 *
 * Produces output in a completely random fashion. Equivalent to not
 * specifying a sequence (i.e. pipe only).
 *
 * MorphModulus (mod)
 * ------------------
 *
 * 		[mymod mod|A ball/Balls/A mighty fireball]
 *
 * Similar to MorphSeqencer, except it wraps around to the beginning when the
 * list is exhausted.
 *
 * MorphRedRndSeq (rrs)
 * --------------------
 *
 * 		[myrrs rrs|A ball/Balls/A mighty fireball]
 *
 * Short for Reducing Random Sequence, I believe. When a particular selection
 * is used, it is removed from the list in subsequent calls. (Like dealing
 * from a deck). When the list is exhausted, the last displayed choice is used.
 *
 * MorphRedRndMod (rrm)
 * --------------------
 *
 * 		[myrrm rrm|A ball/Balls/A mighty fireball]
 *
 * Like rrs above, except that when the list is exhausted it is re-shuffled.
 *
 */

#ifndef __TMORPH_MODULE_
#define __TMORPH_MODULE_


// The following module by Kevin Forchione is required for parsing the
// initializer strings. Get it at www.ifarchive.org or mirrors, as
// if-archive/programming/tads2/examples/parseword.t
#include <parseword.t>

#pragma C+


class morphExpr: object
	expr = '' // The current expression string
	evaluate = {
		local initial, str;
		local lst, ret, i, w, s, intag;

		// Split expression into initializer and list portion
		str = self.expr;
		ret = reSearch('%|', str);
		if (ret == nil) {
			initial = nil;
		} else {
			initial = substr(str, 1, ret[1] -1);
			str = substr(str, ret[1] + 1, length(str));
		}

		// Create list of alternatives (basically copied from tmorph.t):
		lst = []; w = ''; intag = nil;
		for (i = 1; i <= length(str); i++) {
			s = substr(str, i, 1);
#ifdef USE_HTML_PROMPT
			if (s == '<') {intag = true; w += s;}
			else if (s == '>' and intag) {intag = nil; w += s;}
			else
#endif
			if (not intag and s == '/') {
				lst += w; w = '';
			} else w += s;
		}
		lst += w;

		if (initial != nil)
			self.parseInitializer (initial, length(lst));

		return lst[tmorph.choice];
	}
	parseInitializer(str, len) = {
		// Much of this method is a near-copy of the morphTracker function
		// in Kevin's original tmorph.t
		local tk, o, mt = 'rnd';
		local tokenList = parseWord(str);

		if (length(tokenList) < 1) {
			tmorph.choice = _rand(len);
			return;
		}
		tk = tokenList[1];
		if (length(tokenList) == 2) mt = tokenList[2];

		// You can use a number or evaluated expression as an initializer!
		if (cvtnum(tk) > 0) {
			o = cvtnum(tk) % len;
			if (o == 0) o = len;
			tmorph.choice = o;
			return;
		}
	
		o = tmorph.getSequence(tk);
		if (o == nil) {
			switch (mt) {
			case 'seq':
				o = MorphSequencer.instantiate(tk, len);
				break;
			case 'mod':
				o = MorphModulus.instantiate(tk, len);
				break;
			case 'rrm':
				o = MorphRedRandMod.instantiate(tk, len);
				break;
			case 'rrs':
				o = MorphRedRandSeq.instantiate(tk, len);
				break;
			default:
				tmorph.choice = _rand(len);
				return;
			}
		}
		tmorph.choice = o.getval;
	}
;

// tmorph:
// This object carries the necessary global state for the library. I could
// have modified global, but I prefer not to clutter the namespace.

tmorph: object
	choice = 1 // choice variable, set by user or | operator

	curr = nil // Current morph expression
	stack = [] // stack of previous morph expressions
	push = {
		// push current expression onto the stack, begin new expr
		if (self.curr != nil) 
			self.stack = [self.curr] + self.stack;
		self.curr = new morphExpr;
		self.curr.expr = '';
	}
	pop = {
		if (self.curr != nil)
			delete self.curr;
		self.curr = nil;
		if (car(self.stack)) {
			self.curr = car(self.stack);
			self.stack = cdr(self.stack);
		}
	}
	sequences = []
	getSequence (tk) = {
		// This function is also mostly from tmorph.t
		local l = self.sequences;
		local c = car(l);

		while(c) {
			l = cdr(l);
			if (c.name == tk) return c;
			c = car(l);
		}
		return nil;
	}
	inTag = nil // in HTML tag
;

morphFilter: function (s)
{
	local print = '';
	local ret, tmp;

	while (s != '') {
#ifdef USE_HTML_PROMPT
		ret = reSearch('%[|%]|<|>', s);
#else
		ret = reSearch('%[|%]',s);
#endif
		if (ret == nil) { // finished
			if (tmorph.curr == nil) {
				print += s;
				return print;
			} else {
				tmorph.curr.expr += s;
				return print; // print the available part
			}
		}
#ifdef USE_HTML_PROMPT
		if (tmorph.inTag) {
			tmp = substr(s, 1, ret[1]);
			s = substr(s, ret[1]+1, length(s));
			if (tmorph.curr == nil) 
				print += tmp;
			else
				tmorph.curr.expr += tmp;
			if (ret[3] == '>') tmorph.inTag = nil;
		} else if (ret[3] == '<') {
			tmorph.inTag = true;
			tmp = substr(s, 1, ret[1]);
			s = substr(s, ret[1]+1, length(s));
			if (tmorph.curr == nil)
				print += tmp;
			else tmorph.curr.expr += tmp;
		} else if (ret[3] == '>') {
			tmp = substr(s, 1, ret[1]);
			s = substr(s, ret[1]+1, length(s));
			if (tmorph.curr == nil)
				print += tmp;
			else tmorph.curr.expr += tmp;
		} else
#endif		
		if (tmorph.curr==nil and ret[3] == '[') // not in an expression
		{
			print += substr(s, 1, ret[1]-1);
			s = substr(s, ret[1]+1, length(s));
			tmorph.push;
		} else if (tmorph.curr == nil and ret[3] == ']' ){
			return '\b[ERROR: unbalanced square brackets]\b';
		} else if (ret[3] == '[') {
			tmorph.curr.expr += substr(s, 1, ret[1] - 1);
			s = substr(s, ret[1]+1, length(s));
			tmorph.push; // push current expr, begin a new one
		} else if (ret[3] == ']') {
			tmorph.curr.expr += substr(s, 1, ret[1] -1);
			s = substr(s, ret[1] +1, length(s));
			tmp = tmorph.curr.evaluate;
			tmorph.pop; // pop the stack
			if (tmorph.curr == nil) {
				print += tmp;
			} else {
				tmorph.curr.expr += tmp;
			}
		} else {
			return '\b[ERROR: matched a non-bracket!]\b';
		}
	}
	return print;

}

// MorphSequencer and its derivatives are taken with only slight modification
// from the original tmorph.t by Kevin Forchione

class MorphSequencer: object
	len = 0
	val = 0
	name = ''
	getval = {
		self.val++;
		if (self.val > self.len) self.val = self.len;
		return self.val;
	}
	instantiate(tk, l) = {
		local x = new MorphSequencer;
		x.len = l;
		x.name = tk;
		tmorph.sequences += x;
		return x;
	}
;

class MorphRandomizer: MorphSequencer
	getval = {
		self.val = _rand(self.len);
		return self.val;
	}
	instantiate(tk, l) = {
		local x = new MorphRandomizer;
		x.len = l;
		x.name = tk;
		tmorph.sequences += x;

		return x;
	}
;

class MorphModulus: MorphSequencer
	getval = {
		self.val++;
		if (self.val > self.len) self.val = 1;
		return self.val;
	}
	instantiate(tk, l) = {
		local x = new MorphModulus;
		x.len = l;
		x.name = tk;
		tmorph.sequences += x;
		return x;
	}
;

class MorphRedRandMod: MorphSequencer
	rndList = []
	getval = {
		local i, ln;
		ln = length(self.rndList);
		if (ln == 0) {
			// (re)build rndList
			ln = self.len;
			self.rndList = [];
			for (i = 1; i <= ln; ++i)
				self.rndList += i;
		}

		self.val = self.rndList[_rand(ln)];
		self.rndList -= self.val;

		return self.val;
	}
	instantiate(tk, l) = {
		local x = new MorphRedRandMod;

		x.len = l;
		x.name = tk;
		tmorph.sequences += x;
		x.rndList = [];
		return x;
	}
;

class MorphRedRandSeq: MorphSequencer
	rndList = []
	getval = {
		local i, ln;
		ln = length (self.rndList);
		if (ln == 0) {
			return self.val;
		}
		self.val = self.rndList[_rand(ln)];

		self.rndList -= self.val;
		return self.val;
	}
	instantiate(tk, l) = {
		local x = new MorphRedRandSeq;
		local i;

		x.len = l;
		x.name = tk;
		tmorph.sequences += x;

		x.rndList = [];
		for (i = 1; i <= l; ++i)
			x.rndList += i;
		return x;
	}
;

#endif // TMORPH_MODULE

// Below is the copyright information from the original tmorph.t module.

/* Copyright (c) 2000 by Kevin Forchione.  All Rights Reserved. */
/*----------------------------------------------------------------------
 *  COPYRIGHT NOTICE
 *
 *  	You may modify and use this file in any way you want, provided that
 *		if you redistribute modified copies of this file in source form, the
 *   	copies must include the original copyright notice (including this
 *   	paragraph), and must be clearly marked as modified from the original
 *   	version.
 *
 *------------------------------------------------------------------------------
 *  REVISION HISTORY
 *
 *		20-Feb-00:	Creation.
 *      21-Feb-00:  Modified to include init in the function call.
 */

