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