/* 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;
        }
    }
}
