/* Copyright (c) 2000 by Kevin Forchione.  All Rights Reserved. */
/*
 *  TADS ADV.T/STD.T LIBRARY EXTENSION
 *  AGAIN.T				
 *  version 2.0
 *
 *      This module implements a solution to the <<again>> bug that
 *      exists in TADS 2. <<again>> is limited by the fact that TADS
 *      repeats the command based on command objects and not player
 *      command. This can cause problems with commands directed toward
 *      indistinguishable objects.
 *
 *          You see two apples here. 
 *
 *          >eat apple
 *          That was delicious! 
 *          >g
 *          You can't repeat that command.
 *
 *      Again.t remedies this by capturing the command word list
 *      passed to preparseCmd(), then later converting it into a 
 *      command string and prefixing it with the actor vocabulary.
 *
 *          You see two apples here. 
 *
 *          >eat apple
 *          That was delicious! 
 *          >g
 *          That was delicious! 
 *          >g
 *          I don't see any apple here.
 *          
 *
 *      NOTE that capturing the actual words used to address the actor
 *      would complicate the solution. There is, however, the possibility
 *      that a parser-generated error message for an "again" command 
 *      that refers to an actor might use words not contained in the
 *      command. For example:
 *
 *          >x baggins
 *          blah blah blah
 *          baggins has left the area.
 *
 *          >g
 *          There is no bilbo baggins of hobbiton here.
 *
 *----------------------------------------------------------------------
 *  REQUIREMENTS
 *
 *      + HTML TADS 2.5.0 or later
 *      + Should be compatible with ADV.T, WorldClass, Pianosa 
 *        libraries. Alt users will find that version 1.0.6 implements
 *        a similar solution.
 *      + Should be #included after the main library modules.
 *
 *----------------------------------------------------------------------
 *  IMPORTANT LIBRARY INTERFACE AND MODIFICATION
 *
 *      + preparseCmd()
 *      + preCommand()
 *      + compoundWords - special note, if you add a new compoundWord
 *        you must add an equivalent replaceWith() for it in the
 *        cvtFromCompound() method of the Stringizer class.
 *
 *----------------------------------------------------------------------
 *  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
 *
 *	    06-06-00:	Creation.
 *      16-06-00:   Added conversion of compound words.
 */

#ifndef __AGAIN_MODULE_
#define __AGAIN_MODULE_

#pragma C+

/*
 *  The preparseCmd() function saves all command word lists except for
 *  <<again>> or <<g>> in global.cmdWordList. If the command is
 *  <<again>> or <<g>> we replace it with the stored global command
 *  string.
 *
 *  It returns true to indicate that processing is to continue with the
 *  command unchanged.
 */
preparseCmd: function(cmd) {
    if (length(cmd) == 1 
    && (cmd[1] == 'g' || cmd[1] == 'again')) {
        parserReplaceCommand(global.cmdString);
    } else {
        global.cmdWordList = cmd;
    }
    return true;
}

/*
 *  The preCommand() function builds the command string that is then
 *  stored on global. 
 */
preCommand: function(actor, verb, doList, prep, iobj) {
    // Build command string only if command was player generated.
    if (length(global.cmdWordList)) {
        local str;
        str = Stringizer.buildCmdString(actor, global.cmdWordList);
        global.cmdString = str;
    }
}

/*
 *  The Stringizer class converts a word list to a string. In addition
 *  it handles the conversion of TADS special words and the appending of 
 *  actor vocabulary to the front of the string to create a command
 *  string.
 */
class Stringizer: object
    buildCmdString(actor, cmdWordList) = {
        local actorWordList, actorString, cmdWordString, cmdString;
        
        cmdWordList = self.cvtFromSpec(cmdWordList);
        cmdWordString = self.stringize(cmdWordList);
        cmdWordString = self.cvtFromCompound(cmdWordString);
     
    /*
     *  NOTE: getActorVocab() assumes that the actor isn't numbered. If
     *  you find yourself having difficulty with this option #define 
     *  USE_ACTOR_SDESC.
     */
#ifdef USE_ACTOR_SDESC
     
        actorString = self.getActorSdesc(actor);
        
#else /* USE_ACTOR_SDESC */
        
        actorWordList = self.getActorVocab(actor);
        actorString = self.stringize(actorWordList);
        
#endif /* USE_ACTOR_SDESC */
        
        cmdString = actorString + ', ' + cmdWordString;
        return cmdString;
    }
    cvtFromSpec(wordList) = {
        local i, len;
        
        len = length(wordList);
        for (i = 1; i <= len; ++i) {
            switch(wordList[i]) {
                case ',':
                    wordList[i] = 'and';
                    break;
                case 'A':
                    wordList[i] = 'all';
                    break;
                case 'X':
                    wordList[i] = 'but';
                    break;
                case 'I':
                    wordList[i] = 'it';
                    break;
                case 'T':
                    wordList[i] = 'them';
                    break;
                case 'M':
                    wordList[i] = 'him';
                    break;
                case 'R':
                    wordList[i] = 'her';
                    break;
                case 'Y':
                    wordList[i] = 'any';
                    break;
                case 'B': 
                    wordList[i] = 'both';
                    break;
                case 'N':
                    wordList[i] = 'one';
                    break;
                case 'P':
                    wordList[i] = 'ones';
                    break;
            }
        }
        return wordList;
    }
    cvtFromCompound(str) = {
        str = replaceWith(str, '%<onto%>', 'on to');
        str = replaceWith(str, '%<into%>', 'in to');
        str = replaceWith(str, '%<inbetween%>', 'in between');
        str = replaceWith(str, '%<downin%>', 'down in');
        str = replaceWith(str, '%<downon%>', 'down on');
        str = replaceWith(str, '%<upon%>', 'up on');
        str = replaceWith(str, '%<outof%>', 'out of');
        str = replaceWith(str, '%<offof%>', 'off of');
        str = replaceWith(str, '%<i_wide%>', 'i wide');
        str = replaceWith(str, '%<i_tall%>', 'i tall');
        str = replaceWith(str, '%<waitfor%>', 'wait for');
        str = replaceWith(str, '%<waituntil%>', 'wait until');
        return str;
    }
    stringize(wordList) = {
        local i, len, str = '';
        
        len = length(wordList);
        
        for (i = 1; i <= len; ++i) {
            str += wordList[i];
            if (i != len)
                str += ' ';
        }
        return str;
    }
    getActorSdesc(actor) = {
        local stat, str, actorWordList;
        if (actor == parserGetMe()) {
            actorWordList = self.getActorVocab(actor);
            str = self.stringize(actorWordList);
        } else {
            stat = outcapture(true);
            actor.sdesc;
            str = outcapture(stat);
        }
        return str;
    }
    getActorVocab(actor) = {
        local actorWordList = [];
        
        actorWordList += getwords(actor, &adjective);
        if (actor.isThem)
            actorWordList += getwords(actor, &plural)[1];
        else actorWordList += getwords(actor, &noun)[1];
        return actorWordList;
    }
;


#ifndef __REPLACEWITH_MODULE_
#define __REPLACEWITH_MODULE_

#define RW_REPLACE_ONCE     1
#define RW_MATCH_WORD       2
#define RW_MATCH_CASE       4
#define RW_RET_NIL          8

/*
 *  replaceWith(value, target, replacement, flags)
 *
 *  The function searches the value string, replacing the target string
 *  with the replacement string. 
 *
 *  Bit-flags can be passed to control the search. 
 *
 *      RW_REPLACE_ONCE replace only one occurrence of the target in the 
 *           value string.
 *
 *          The default for replaceWith() is to replace all occurrences 
 *          of the target in the value string.
 *
 *      RW_MATCH_WORD target must match whole words. For example:
 *
 *          target 'get in' will search for '%<get>% *%<in%>'
 *          which will match 'get    in the chair', but not 
 *          'get into the chair'
 *
 *      RW_MATCH_CASE target must match the case in value. 
 *
 *          The default for replaceWith() is case-insensitive. A target
 *          string of 'get into' will match on 'GET INTO THE CAR' as
 *          well as 'Get into the car' unless RW_MATCH_CASE is used. 
 *
 *      RW_RET_NIL function returns nil if no match for the target found
 *
 *          The default for replaceWith() returns the value unchanged.
 *          Using RW_RET_NIL will return the value only if replacement
 *          occurred; otherwise the function will return nil.
 */
replaceWith: function(value, target, replacement, ...)
{
    local ret, new_value = '';
    local valuesave, targetsave, replacementsave;
    local flags = 0;
    
    if (argcount > 3) flags = getarg(4);
    if ((flags & RW_MATCH_WORD)!= 0) 
    {
        local tmptarget = '%<';
        tmptarget += replaceWith(target, ' ', '%> *%<');
        tmptarget += '%>';
        target = tmptarget;
    }
    
    do
    {
        if ((flags & RW_MATCH_CASE) == 0)
        {
            valuesave = value;
            targetsave = target;
            replacementsave = replacement;
            value = lower(value);
            target = lower(target);
            replacement = lower(replacement);
        }
        ret = reSearch(target, value);
        if ((flags & RW_MATCH_CASE) == 0)
        {
            value = valuesave;
            target = targetsave;
            replacement = replacementsave;
        }
        if (ret)
        {
            local len, tmp = '';
            len = length(value) + 1;
            if (ret[1] - 1)
                tmp += substr(value, 1, ret[1]-1);
            tmp += replacement;
            new_value += tmp;
            if (len - (ret[1]+ret[2]))
                value = substr(value, ret[1]+ret[2], len - (ret[1]+ret[2]));
            else
                value = '';
            if ((flags & RW_REPLACE_ONCE) != 0) break;
        }
        else if ((flags & RW_RET_NIL)!= 0
            && new_value == '') 
                return nil;
    }
    while( ret != nil);
    new_value += value;
    return new_value;
}

#pragma C-

#endif  /* __REPLACEWITH_MODULE_ */

#endif /* __AGAIN_MODULE_ */
