#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:[email protected]">
                 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)];
}