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