/* Copyright (c) 1999, 2000 by Kevin Forchione.  All Rights Reserved. */
/*
*  TADS ADV.T/STD.T LIBRARY EXTENSION
*  SMARTLIST.T
*  version 1.0
*
*      smartlist.t provides an enhancement of the standard ADV.T listing
*  functions. These enhancements include:
*
*      smartlist
*          Useful in debugging, smartlist displays objects of various
*          datatypes.
*      listcont
*      listcontcont
*          The use of lists or objects as function parameters
*      showcontcont
*      itemXcnt
*      listXcont
*          Display of Actors in nestedrooms
*      listfixedcont
*          Display of fixeditems in room listings when Actor is inside
*          of enterable class objects.
*
*----------------------------------------------------------------------
*  REQUIREMENTS
*
*      + HTML TADS 2.2.6 or later
*      + Requires ADV.T and STD.T
*      + Should be #included after ADV.T and STD.T.
*
*----------------------------------------------------------------------
*  IMPORTANT LIBRARY INTERFACE AND MODIFICATION
*
*      This module modifies the functioning of the following ADV.T
*      listing functions:
*
*          + listcont()
*          + listcontcont()
*          + showcontcont()
*
*----------------------------------------------------------------------
*  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
*
*              12-May-99:      Creation.
*/

#define __SMARTLIST_MODULE_


smartlist: function;
listfixedcont: function;
itemXcnt: function;
listXcont: function;

/*
*      smartlist: function( list, origlen )
*
*      This is a recursive function that is useful for displaying
*      information in nested lists. It will separate each item by
*      a space, and separate sublists with '[ ... ]' indicators.
*
*      Method is passed a list and the length of the list, which is
*      used to determine spacing.
*/
smartlist: function( list, origlen )
{
       local o;

       if ( list = nil or length( list ) = 0 ) return;

   if ( origlen = 2 )
   {
       if ( length( list ) < origlen )
           " and ";
   }
   else if ( origlen > 2 )
   {
       if ( length( list ) = 1 )
           ", and ";
       else if ( length( list ) < origlen )
           ", ";
   }

       o := car( list );
       switch( datatype( o ) )
       {
               case 1: // number
                       say( o );
                       break;
               case 2: // object
                   if ( proptype( o, &sdesc ) <> 5 )
                               o.sdesc;
                       else
                               "(unnamed_object)";
                       break;
               case 3: // string
                       say( o );
                       break;
               case 5: // nil
                       "(nil)";
                       break;
               case 7: // list
                       "[";
                       smartlist( o, length( o ) );
                       "]";
                       break;
               case 8: // true;
                       "(true)";
                       break;
               case 10:        // function pointer
                       "(function_pointer)";
                       break;
               case 13:        // property pointer
                       "(property_pointer)";
                       break;
               default:
       }

       list := cdr( list );
       smartlist( list, origlen );
}

/*
*  listfixedcont: function(obj)
*
*  This function is similar to listcont(obj).
*
*  This function displays the fixeditem / actor contents of an
*      object/list, separated by commas.  The thedesc properties of the
*      contents are used. It is up to the caller to provide the
*      introduction to the list (usually something to the effect of "The
*      box contains" is displayed before calling listXfixedcont) and
*      finishing the sentence (usually by displaying a period).  An object
*      is listed only if its isactor or isfixed property is true.  If there
*      are multiple indistinguishable items in the list, the items are
*  listed only once (with the number of the items).
*/
listfixedcont: function(obj)
{
   local i, count, tot, list, cur, disptot := 0, prefix_count;

       if (datatype(obj) = 2)
               list := obj.contents;
       else
               list := obj;
   tot := length(list);
   count := 0;
   for (i := 1; i <= tot; ++i)
   {
       if ( list[i].isactor or list[i].isfixed )
           disptot++;
   }
   for (i := 1 ; i <= tot ; ++i)
   {
       cur := list[i];
       if (cur.isactor or cur.isfixed)
       {
           /* presume there is only one such object */
           prefix_count := 1;

           /*
            *   if this is one of more than one equivalent items, list it
            *   only if it's the first one, and show the number of such
            *   items along with the first one
            */
           if (cur.isEquivalent)
           {
               local before, after;
               local j;
               local sc;

               sc := firstsc(cur);
               for (before := after := 0, j := 1 ; j <= tot ; ++j)
               {
                   if (isIndistinguishable(cur, list[j]))
                   {
                       if (j < i)
                       {
                           /*
                            *   note that objects precede this one, and
                            *   then look no further, since we're just
                            *   going to skip this item anyway
                            */
                           ++before;
                           break;
                       }
                       else
                           ++after;
                   }
               }

               /*
                *   if there are multiple such objects, and this is the
                *   first such object, list it with the count prefixed;
                *   if there are multiple and this isn't the first one,
                *   skip it; otherwise, go on as normal
                */
               if (before = 0)
                   prefix_count := after;
               else
                   continue;
           }

           if (count > 0)
           {
               if (count+1 < disptot)
                   ", ";
               else if (count = 1)
                   " and ";
               else
                   ", and ";
           }

           /* list the object, along with the number of such items */
           if (prefix_count = 1)
               cur.adesc;
           else
           {
               sayPrefixCount(prefix_count); " ";
               cur.pluraldesc;
           }

           /* show any additional information about the item */
           if (cur.isworn)
               " (being worn)";
           if (cur.islamp and cur.islit)
               " (providing light)";
           count := count + 1;
       }
   }
}

/*
*  listcont: function(obj)
*
*  This function performs exactly as ADV.T listcont(obj). The
*  only difference is that it accepts a list or an object.
*
*  This function displays the contents of an object/list, separated
*  by commas.  The thedesc properties of the contents are used.
*  It is up to the caller to provide the introduction to the list
*  (usually something to the effect of "The box contains" is
*  displayed before calling listXcont) and finishing the
*  sentence (usually by displaying a period).  An object is listed
*  only if its isListed property is true.  If there are
*  multiple indistinguishable items in the list, the items are
*  listed only once (with the number of the items).
*/
replace listcont: function(obj)
{
   local i, count, tot, list, cur, disptot, prefix_count;

       if (datatype(obj) = 2 )
       list := obj.contents;
   else
       list := obj;

   tot := length(list);
   count := 0;
   disptot := itemcnt(list);
   for (i := 1 ; i <= tot ; ++i)
   {
       cur := list[i];
       if (cur.isListed)
       {
           /* presume there is only one such object */
           prefix_count := 1;

           /*
            *   if this is one of more than one equivalent items, list it
            *   only if it's the first one, and show the number of such
            *   items along with the first one
            */
           if (cur.isEquivalent)
           {
               local before, after;
               local j;
               local sc;

               sc := firstsc(cur);
               for (before := after := 0, j := 1 ; j <= tot ; ++j)
               {
                   if (isIndistinguishable(cur, list[j]))
                   {
                       if (j < i)
                       {
                           /*
                            *   note that objects precede this one, and
                            *   then look no further, since we're just
                            *   going to skip this item anyway
                            */
                           ++before;
                           break;
                       }
                       else
                           ++after;
                   }
               }

               /*
                *   if there are multiple such objects, and this is the
                *   first such object, list it with the count prefixed;
                *   if there are multiple and this isn't the first one,
                *   skip it; otherwise, go on as normal
                */
               if (before = 0)
                   prefix_count := after;
               else
                   continue;
           }

           if (count > 0)
           {
               if (count+1 < disptot)
                   ", ";
               else if (count = 1)
                   " and ";
               else
                   ", and ";
           }

           /* list the object, along with the number of such items */
           if (prefix_count = 1)
               cur.adesc;
           else
           {
               sayPrefixCount(prefix_count); " ";
               cur.pluraldesc;
           }

           /* show any additional information about the item */
           if (cur.isworn)
               " (being worn)";
           if (cur.islamp and cur.islit)
               " (providing light)";
           count := count + 1;
       }
   }
}

/*
*  listcontcont: function(obj)
*
*  This function performs exactly as ADV.T listcontcont(obj). The
*  only difference is that it accepts a list or an object.
*
*  This function lists the contents of the contents of an
*  object/list. It displays full sentences, so no introductory or
*  closing text is required.  Any item in the contents list of the
*  object obj whose contentsVisible property is true has
*  its contents listed.  An Object whose isqcontainer or
*  isqsurface property is true will not have its
*  contents listed.
*/
replace listcontcont: function(obj)
{
   local list, i, tot;

   if (datatype(obj) = 2)
       list := obj.contents;
   else
       list := obj;
   tot := length(list);
   i := 1;
   while (i <= tot)
   {
       showcontcont(list[i]);
       i := i + 1;
   }
}

/*------------------------------------------------------------------------------
*      FUNCTIONS USED TO DISPLAY ACTORS IN NESTED ROOMS
*----------------------------------------------------------------------------*/

/*
*   This function has been modified to use the new itemXcnt() and
*       listXcont() functions, which take into account items and Actors.
*/
replace showcontcont: function(obj)
{
   if (itemXcnt(obj.contents))
   {
       if (obj.issurface)
       {
           if (not obj.isqsurface)
           {
               "Sitting on "; obj.thedesc;" is "; listXcont(obj);
               ". ";
           }
       }
       else if (obj.contentsVisible and not obj.isqcontainer)
       {
           caps();
           obj.thedesc; " seem";
           if (!obj.isThem) "s";
           " to contain ";
           listXcont(obj);
           ". ";
       }
   }
   if (obj.contentsVisible and not obj.isqcontainer)
       listfixedcontcont(obj);
}

/*
*   itemXcnt: function( list )
*
*   This functions exactly as itemcnt() does, except that it includes
*       Actors into its totals. Any object that has .isactor = true will
*       be included.
*/
itemXcnt: function(list)
{
   local cnt, tot, i, obj, j;

   tot := length(list);
   for (i := 1, cnt := 0 ; i <= tot ; ++i)
   {
       /* only consider this item if it's to be listed */
       obj := list[i];
       if (obj.isListed or obj.isactor)
       {
           /*
            *   see if there are other equivalent items later in the
            *   list - if so, don't count it (this ensures that each such
            *   item is counted only once, since only the last such item
            *   in the list will be counted)
            */
           if (obj.isEquivalent)
           {
               local sc;

               sc := firstsc(obj);
               for (j := i + 1 ; j <= tot ; ++j)
               {
                   if (isIndistinguishable(obj, list[j]))
                       goto skip_this_item;
               }
           }

           /* count this item */
           ++cnt;

       skip_this_item: ;
       }
   }
   return cnt;
}

/*
*   listXcont: function( obj )
*
*   This functions exactly as listcont(), except that it takes Actors into
*       account. We don't replace listcont(), however, because it's only when
*       actors are inside nested rooms that we want to list them in this manner.
*       When they are inside the player's location
*   we use the normal method.
*/
listXcont: function(obj)
{
   local i, count, tot, list, cur, disptot, prefix_count;

       if (datatype(obj) = 2)
       list := obj.contents;
   else
       list := obj;
   tot := length(list);
   count := 0;
   disptot := itemXcnt(list);
   for (i := 1 ; i <= tot ; ++i)
   {
       cur := list[i];
       if (cur.isListed or cur.isactor)
       {
           /* presume there is only one such object */
           prefix_count := 1;

           /*
            *   if this is one of more than one equivalent items, list it
            *   only if it's the first one, and show the number of such
            *   items along with the first one
            */
           if (cur.isEquivalent)
           {
               local before, after;
               local j;
               local sc;

               sc := firstsc(cur);
               for (before := after := 0, j := 1 ; j <= tot ; ++j)
               {
                   if (isIndistinguishable(cur, list[j]))
                   {
                       if (j < i)
                       {
                           /*
                            *   note that objects precede this one, and
                            *   then look no further, since we're just
                            *   going to skip this item anyway
                            */
                           ++before;
                           break;
                       }
                       else
                           ++after;
                   }
               }

               /*
                *   if there are multiple such objects, and this is the
                *   first such object, list it with the count prefixed;
                *   if there are multiple and this isn't the first one,
                *   skip it; otherwise, go on as normal
                */
               if (before = 0)
                   prefix_count := after;
               else
                   continue;
           }

           if (count > 0)
           {
               if (count+1 < disptot)
                   ", ";
               else if (count = 1)
                   " and ";
               else
                   ", and ";
           }

           /* list the object, along with the number of such items */
           if (prefix_count = 1)
               cur.adesc;
           else
           {
               sayPrefixCount(prefix_count); " ";
               cur.pluraldesc;
           }

           /* show any additional information about the item */
           if (cur.isworn)
               " (being worn)";
           if (cur.islamp and cur.islit)
               " (providing light)";
           count := count + 1;
       }
   }
}