/*************************************************************************
 * Possess.t: Using possessives as adjectives.
 *
 * By Garth Dighton, with help from Mike Roberts and Kevin Forchione on RAIF
 * This module is freely distributable and modifiable, as long as the credits
 * remain intact.
 *
 * This module enables players to refer to objects in the possession of actors
 * with possessive adjectives: "ASK JANE ABOUT HER BOX, ASK BOB ABOUT GEORGE'S
 * BOX", or similar.
 *
 * To use, each Actor should have the possessives property -- a list of
 * single-quoted words which are added to the adjectives list of objects they
 * carry. 'Her' and 'his' can be ignored.

 * Example:
 * Jane: Actor
 *      noun = 'jane'
 *      adjective = 'plain'
 *      sdesc = "Jane"
 *      possessives = ['jane\'s' 'girl\'s']
 * ;

 * If any of these functions are overridden in your code, you'll have to make
 * adjustments:
 * thing.moveInto
 * parseNounPhrase
 * parseDisambig

 * Functions added
 * thing.Grab() -- Hook called when something is removed from an object's
 *  possession.
 * thing.Accept() -- Hook called when somthing is moved into an object's
 *  possession.
 
 * These functions are overridden in Actor to provides the necessary
 * functionality. You can still add your own code, as long as the Actor code
 * gets called
 
 *******************************************************************/

#ifndef POSSESS_T
#define POSSESS_T

#pragma C+

modify thing
	// Add an Accept(obj) hook to moveInto:
    moveInto(obj) =
    {
        local loc;

        /*
         *   For the object containing me, and its container, and so forth,
         *   tell it via a Grab message that I'm going away.
         */
        loc = self.location;
        while (loc)
        {
            loc.Grab(self);
            loc = loc.location;
        }
        
        if (self.location)
            self.location.contents = self.location.contents - self;
        self.location = obj;
        if (obj) {
            obj.contents = obj.contents + self;

			loc = self.location;
			while (loc)
			{
				loc.Accept(self);
				loc = loc.location;
			}
		}	
    }
	Grab(obj) = {}
	Accept(obj) = {}
	
;

modify Actor
	Grab(obj) = {
		local i;
		for (i = 1; i <= length(self.possessives); i++) {
			delword(obj, &adjective, self.possessives[i]);
		}
	}
	Accept(obj) = {
		local i;
		for (i = 1; i <= length(self.possessives); i++) {
			addword(obj, &adjective, self.possessives[i]);
		}
	}
;


// Returns true if the word is a noun of the object
// Used to eliminate adjective-only matches. Necessary because parseNounList
// does _not_ seem to return the correct value for PRSFLG_ENDADJ!!!
isNounOf: function (word, obj)
{
    local lst = getwords(obj, &noun);
    if (find(lst, word)) return true; else return nil;
}


// parseNounPhrase function by Kevin Forchione and Mike Roberts, in response
// to a request for help made on rai-f.

// Modified to handle 'his' (though not 'my' or other possessives). Also
// modified to check that the objects exist within a valid person (ideally the
// current 'her' or 'him').

// Further modified to eliminate matches ending in adjectives if there's any
// other match.
parseNounPhrase: function(wordList, typeList, current_index,
                            complain_on_no_match, is_actor_check)
{
    local i, next_index, next_token, ret, lst = [];
	local pos = parserGetObj(PO_HER);
	local found = nil;
	local gender = &isHer;
    local eliminateAdj = nil;
    local lastword = nil;

    /* let the parser handle noun phrases that don't start with 'her' */
    if (wordList[current_index] != 'R' and wordList[current_index] != 'his')
        return PNP_USE_DEFAULT;

	if (wordList[current_index] != 'R') {
		pos = parserGetObj(PO_HIM);
		gender = &isHim;
	}

    /*
     *  'her' is the last word of the noun phrase, so it is not
     *  being used as a possessive pronoun.
     */
    if (length(wordList) == current_index)
        return PNP_USE_DEFAULT;

    /* set the next_index */
    next_index = current_index + 1;

    /* get the next word's type */
    next_token = typeList[next_index];

    /*
     *  If the next word is an article then 'her' is not being used as a
     *  possessive pronoun.
     */
    if ((next_token & PRSTYP_ARTICLE) != 0)
        return PNP_USE_DEFAULT;
    /*
     *  If the next word cannot be an adjective, noun, or plural then
     *  'her' is not being used as a possessive pronoun.
     */
    if ((next_token & PRSTYP_ADJ) == 0
    && (next_token & PRSTYP_NOUN) == 0
    && (next_token & PRSTYP_PLURAL) == 0)
        return PNP_USE_DEFAULT;

    ret = parseNounList(wordList, typeList, next_index,
                       nil, nil, nil);

    /*
     *  We don't have a syntactically valid noun phrase, so assume
     *  that 'her' is not being used as a possessive pronoun.
     */
    if (ret == nil)
        return PNP_USE_DEFAULT;

    /*
     *  There are 2 cases to examine: no noun phrase or a syntactically
     *  valid noun phrase, but with no matching objects.
     */
    if (length(ret) == 1)
        /*
         *  No noun phrase. The first word isn't a noun, adjective,
         *  article, etc.
         */
        if (ret[1] == current_index)
            return PNP_USE_DEFAULT;
        else {
			goto noObjects;
        }

    /*
     *  We have a valid noun phrase, let's assume that 'her' is being
     *  used as a possessive. We'll restructure the return from
     *  parseNounList() so that it can be returned from
     *  parseNounPhrase().
     */
    lst += ret[1];
    lastword = wordList[ret[2][2]];

	/*	First we see if there are any objects within the current "her" 
	 *	object. If so, we will return those possible objects only.
     *
     *  We also note if the match ended with a noun -- if so, we will later
     *  eliminate all adjective-only matches
	 */
    for (i = 3; i <= length(ret[2]); i += 2) {
		if (not ret[2][i].isIn(pos)) continue;
        lst += ret[2][i];
		lst += ret[2][i+1];
        if (isNounOf(lastword, ret[2][i])) eliminateAdj = true;
		found = true;
	}

	/*	If there weren't any valid objects within 'her', check for objects
	 * 	within _any_ female.
	 */
	if (not found) {
		for (i = 3; i < length(ret[2]); i += 2) {
			if (ret[2][i].location == nil or not ret[2][i].location.(gender)) 
				continue;
			lst += ret[2][i];
			lst += ret[2][i+1];
            if (isNounOf(lastword, ret[2][i])) eliminateAdj = true;
			found = true;
		}
	}

	if (not found) goto noObjects;

    if (not eliminateAdj) return lst;

    // Eliminate any match which did not end in a noun. We use "ret" instead
    // of "lst" for the return value in this case.
    ret = [] + lst[1];
    for (i = 2; i <= length(lst); i+=2) {
        if (isNounOf(lastword, lst[i])) {
            ret += lst[i];
            ret += lst[i+1];
        }
    }

    return ret;

noObjects:
	/*
	 *  Syntactically valid noun phrase with no matching objects!
	 */
	"I don't see any ";
	for (i = next_index; i <= length(wordList); ++i)
	{
		say(wordList[i]);
		" ";
	}
	"here. ";
	return PNP_ERROR;

}

parseDisambig: function(str, lst)
{
	local i, tot, cnt;
	local mystr = str;

	// Remove possessives (R, his, my, and words ending in 's) from the noun
	// phrase before asking. This is particularly necessary in the case of R,
	// because "Which R box do you mean..." is particularly annoying!

	local match = reSearch('R |his |my |%<%w*\'s%>', str);
	if (match) {
		// using length(str) for the length of the second substr will
		// guarantee that it goes to the end-of-string. A hack, but easier
		// than calculating.
		mystr = substr(str, 1, match[1]-1) + 
			substr(str, match[1]+match[2], length(str)); 

	}
	"Which << mystr >> do you mean, ";
	for (i = 1, cnt = length(lst) ; i <= cnt ; ++i)
	{
	   lst[i].thedesc;
	   if (i < cnt) ", ";
	   if (i + 1 == cnt) "or ";
	}
	"?";
}

#ifdef VERSION
// versionTag for Jeff Laing's version.t module
possessiveVersion: versionTag
    author = 'Garth Dighton'
    func = 'possessive handling'
;
#endif VERSION

#endif
