#charset "us-ascii"

#include <adv3.h>
#include <en_us.h>

 /* 
  *   listControl 
  *.  version 0.2 (Beta) : 12-Jul-09 
  *.  by Eric Eve
  */

//------------------------------------------------------------------------------

/* 
 *   listControl provides three new functions:
 *.     stop(lst)
 *.     cycle(lst)
 *.     shuffle(lst)
 *.
 *   The lst parameter can be supplied either as a single list (in square 
 *   brackets) or as a number of parameters separated by commas: in other 
 *   words the following two function calls are equivalent:
 *.
 *.     stop(['screams', 'yells', 'cries again'])
 *.     stop('screams', 'yells', 'cries again')
 *.
 *   The purpose of these functions is typically to provide minor variations 
 *   of wording in larger pieces of text, for example:
 *.
 *.   "<q>Absolutely not!</q> Bob <<stop('declares', 'insists', 'declares yet
 *.     again')>>. ";
 *.
 *   These functions are thus a little similar to the rand() function already
 *   built in to TADS 3, and have been designed to use analogous syntax.
 *.
 *.     stop() works like a StopEventList
 *.     cycle() works like a CyclicEventList
 *.     shuffle() works like a ShuffledEventList
 *.
 *   IMPORTANT NOTE. In order for these three functions to work, the 
 *   extension has to store data in relation to each list (each three 
 *   function needs to keep track of where in the list it has got to). For 
 *   this purpose, the extension needs some way of identifying which list is 
 *   which, and it does this by looking at the elements of the list. It 
 *   follows that if there are two lists with the same elements in your 
 *   game, they'll be treated as if they were the same list. This may not 
 *   matter for shuffle() but will matter a lot for stop().
 *
 *   If this is likely to be an issue for your game, you can make each list 
 *   that might otherwise be duplicated unique by adding a first element 
 *   that's of a different data type from all the rest. For example, you 
 *   could use a number or an object as the first element of a list of 
 *   strings to make it a unique list of strings - the stop(), cycle(), and 
 *   shuffle() functions will know to ignore this first item in their 
 *   output. Thus, one way to ensure a stop() function treats its list as 
 *   unique when it's used in a TopicEntry, say, might be to add 'self' as 
 *   the first element of the list:
 *
 *.    "<q>Absolutely not!</q> Bob <<stop(self, 'declares', 'insists', 
 *.     'declares yet again')>>. ";
 *.
 *   This will work because 'self' will refer to a different object for every
 *   TopicEntry, thus guaranteeing to make the list unique (unless you use 
 *   the same list more than once in the same TopicEntry, in which case you 
 *   could distinguish them by making the first element [self, 1] then 
 *   [self, 2] and so on).
 */

ModuleID
    name = 'list control'
    byline = 'by Eric Eve'
    htmlByline = 'by <a href="mailto:eric.eve@hmc.ox.ac.uk">
                  Eric Eve</a>'
    version = '0.2'
    listingOrder = 70
;



//==============================================================================

/* 
 *   The listControl object stores three lookup tables for use with the 
 *   stop(), cycle() and shuffle() functions, together with some common code 
 *   they each use.
 *
 *   This works by storing a reference to the list for which we want the next
 *   item (in stopping, cyclic or shuffled order) in the appropriate 
 *   LookupTable together with the current index value. The next index value 
 *   is then calculated according to the kind of list, stored in the table 
 *   and returned to the caller. We thus use the LookupTables to associate a 
 *   numerical index value with each list.
 */

listControl: object
    
    /* 
     *   The LookupTables for use with the stop(), cycle() and shuffle() 
     *   functions respectively
     */
    stopTab = nil
    cycleTab = nil
    shuffleTab = nil
    
    /* 
     *   If the first element of the list is not of the same type as the 
     *   second, then we assume that the first element is simply a marker to 
     *   make the list unique, in which case we don't want to include it 
     *   among the items to be returned from the list, so we start with the 
     *   second element.
     */
    
    getFirstIdx(lst)
    {
        if(lst.length > 1 && dataType(lst[1]) != dataType(lst[2]))
            return 2;
        return 1;
    }
    
    /* 
     *   Find the appropriate LookupTable for the kind of list we want to 
     *   reference, and create one if one does not already exist.
     */
    
    getTable(prop)
    {
        local tab = self.(prop);
        if(tab == nil)
        {
            self.(prop) = new LookupTable(10,10);
            tab = self.(prop);
        }
        return tab;
    }
    
    /* Return the index of the next item we want from the current list */
    
    getNextIdx(lst, tabProp, nextProp)
    {
        local tab = getTable(&tabProp);
        local idx = tab[lst];
        
        if(idx == nil)    
            idx = getFirstIdx(lst);
        else
        {
            idx++;
            if(idx > lst.length())
                idx = self.(nextProp)(lst);
        }
        
        tab[lst] = idx;
        return idx;
    }
    
    getNextStopIdx(lst) { return lst.length(); }
    getNextCycleIdx(lst) { return getFirstIdx(lst); }
    
;


/* 
 *   Each of these three functions provides a means of making the stored list
 *   unique; if the first element of the list is of a different datatype 
 *   from the second, then the first element will be ignored in creating the 
 *   list but stored as part of the key identifying the list.
 */

/* 
 *   In the case of the shuffle function we'll leverage the library's 
 *   ShuffledList class. The function creates a new ShuffledList 
 *   corresponding to each list it's passed and stores a refence to it in a 
 *   LookupTable. The ShuffledList's getNextValue method is then used to 
 *   return the next value from the list.
 */

shuffle([lst])
{
    if(lst.length == 1 && dataType(lst[1]) == TypeList)
       lst = lst[1];
        
    local tab = listControl.getTable(&shuffleTab);
    
    local shufList = tab[lst];
    local modifiedLst = lst;
    
    if(shufList == nil)
    {
        /* 
         *   If the first item in the list is of a different type from the 
         *   second, then the first item is simply a marker to make the list 
         *   unique, so we don't want it included in the list of items from 
         *   which a value will be returned.
         */
        if(lst.length > 1 && dataType(lst[1]) != dataType(lst[2]))
            modifiedLst = lst.cdr();
        
        shufList = new ShuffledList(modifiedLst);
        tab[lst] = shufList;
    }
    
    return shufList.getNextValue();
}

/* 
 *   The next two functions are very similar, so to avoid duplicated code 
 *   they simple call the appropriate methods on listControl.
 */

stop([lst])
{        
    if(lst.length == 1 && dataType(lst[1]) == TypeList)
       lst = lst[1];
       
    return lst[listControl.getNextIdx(lst, &stopTab, &getNextStopIdx)];
}

cycle([lst])
{
    if(lst.length == 1 && dataType(lst[1]) == TypeList)
       lst = lst[1];
       
    return lst[listControl.getNextIdx(lst, &cycleTab, &getNextCycleIdx)];            
}

