#include <adv3.h>
#include <en_us.h>

#ifndef LANGHEADER
    #define LANGHEADER <LANGUAGE.h>
#endif
#include LANGHEADER

/*
 *   Reaction objects extension.
 *
 *   This creates BeforeAction and AfterAction objects, and a framework to use
 *   them, rather like AskTellTopic. The idea is that instead of filling your
 *   beforeAction and afterAction methods with sphaghetti code, all you have to
 *   do is define a set of BeforeAction and AfterAction objects and nest them in
 *   the objects that needs to react to things.
 *
 *   The main two types of Reaction objects are BeforeAction and AfterAction.
 *   These can be nested in any kind of Thing in your game world, as well as in
 *   any ActorState. The extension also provides RoomBeforeAction and
 *   RoomAfterAction objects, which can be used with any Room (including
 *   NestedRooms).
 *
 *   All Reaction objects provide the same set of properties. actor, action,
 *   dobj, and iobj allow you to decide when the Reaction should happen. If they
 *   are defined, they must match the current action before the Reaction will
 *   run. They can be either a single object or a list of objects. If you use a
 *   list, matching any of the items in the list will allow the Reaction to run.
 *   You can also define a condition, which lets you add any extra restrictions
 *   you want.
 *
 *   If isOneOff is true, the reaction will only happen once; otherwise it will
 *   happen every time it is active. If isDone is true, the reaction will no
 *   longer run. If isOneOff is true, isDone will be automatically set when the
 *   reaction first runs, but you can also set it manually if you want to. If 
 *   stopAction is true, the reaction will exit() the current action.
 *
 *   When a Reaction is active, it will run the code in its react() method. 
 *   Alternatively, you can create a Reaction that also inherits from an 
 *   EventList class; in that case, the EventList will run when the Reaction is 
 *   active.
 *
 *   You can determine the order Reactions happen in by changing their priority.
 *   The higher the priority, the sooner the reaction will happen. The default 
 *   is 100.
 *
 *   Some example code:
 *
 *   linda: Person
 *     'linda'
 *     'Linda'
 *     isProperName = true
 *     isHer = true
 *     isMad = nil
 *   ;
 *
 *   + AfterAction
 *      actor = gPlayerChar
 *      action = ExamineAction
 *      dobj = linda
 *      isOneOff = true
 *      react() {
 *        "Linda catches your eye. <q>Hey, quit starin' at me!</q> she snaps. ";
 *        linda.isMad = true;
 *      }
 *   ;
 *
 *   + AfterAction
 *     actor = gPlayerChar
 *     action = ExamineAction
 *     dobj = linda
 *     condition = (linda.isMad)
 *     isOneOff = true
 *     priority = 150  // runs earlier than the other AfterActions
 *     react() {
 *       "You don't even see Linda's fist coming. Flat on your back, you stare
 *       up at her in confusion.<.p>
 *       <q>I told you to keep your sneaky eyes off me!</q> she says, and 
 *       stalks out of the room. ";
 *       linda.moveIntoForTravel(nil);
 *       gPlayerChar.hasHeadache = true;
 *     }
 *
 *   + BeforeAction
 *     actor = gPlayerChar
 *     action = TakeAction
 *     dobj = stackOfCash
 *     stopAction = true
 *     react() {
 *       "Linda slaps your hand away. <q>Hands off!</q> ";
 *     }
 *   ;
 *
 *   This extension is open source software licensed under the MIT Licence:
 *
 *   Copyright (c) 2013 Emily Boegheim
 *
 *   Permission is hereby granted, free of charge, to any person obtaining a 
 *   copy of this software and associated documentation files (the "Software"), 
 *   to deal in the Software without restriction, including without limitation 
 *   the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 *   and/or sell copies of the Software, and to permit persons to whom the 
 *   Software is furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in 
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *   DEALINGS IN THE SOFTWARE.
 *
 *   Change history:
 *
 *   0.2
 *   - changed priority ordering from lowest-first to highest-first and made the
 *     default priority 100 to match similar properties in adv3
 *   - simplified handling of the "condition" property
 *   - fixed a remarkably stupid bug where I'd done all the work to implement
 *     RoomBeforeAction and RoomAfterAction classes except, y'know, actually
 *     *implement* those classes
 *   - changed Reaction.isActive to always return nil if isDone is true 
 *     (previously this only worked if isOneOff was also true, but authors
 *     might want to set isDone manually on other kinds of Reactions as well)
 *   - added credits module
 *
 *   0.1
 *   - first public beta release
 */


/* Info on the extension, so it will show up in the list of credits. */
reactionsModuleID: ModuleID
    name = 'Reactions'
    byline = 'by Emily Boegheim'
    htmlByline = 'by <a href="mailto:emily.boegheim@gmail.com">Emily
        Boegheim</a>'
    version = '0.2'
;


/* 
 *   First the base class: Reaction. Provides default properties and isActive 
 *   handling.
 */
class Reaction: object
    actor = nil
    action = nil
    dobj = nil
    iobj = nil
    condition = true
    
    /* some reactions should only happen once */
    isOneOff = nil
    isDone = nil
    
    /* 
     *   Which list should this Reaction belong to? This needs to be overridden
     *   for each subclass. Authors using the extension shouldn't need to change
     *   this unless they are creating their own Reaction subclasses.
     */
    reactionObjList = nil
    
    /* 
     *   Check to see if the Reaction is active. By default, a Reaction is
     *   active when, if any of its action-matching properties (actor, action,
     *   dobj, iobj) have a value, those defined match those in existence. The
     *   properties can be either lists (in which case any of the items in the
     *   list may match) or a single object/class (in which case it must match
     *   specifically).
     *
     *   This class also provides a "condition" property, which must evaluate
     *   to true.
     *
     *   For more complex conditions, authors may simply override this function
     *   to return whatever result they wish.
     */
    isActive()
    {
        /* not if it's over and done with */
        if (isDone)
            return nil;
        
        /* check the conditions */
        if (actor != nil && ((actor.ofKind(List) && !actor.indexOf(gActor))
                             || (actor.ofKind(Thing) && actor != gActor)))
            return nil;
        if (action != nil && ((action.ofKind(List) && !actionInList()) ||
                              (action.ofKind(Action) && !gAction.ofKind(action))))
            return nil;
        if (dobj != nil && ((dobj.ofKind(List) && !dobj.indexOf(gDobj))
                             || (dobj.ofKind(Thing) && dobj != gDobj)))
            return nil;
        if (iobj != nil && ((iobj.ofKind(List) && !iobj.indexOf(gIobj))
                             || (iobj.ofKind(Thing) && iobj != gIobj)))
            return nil;
        return condition;
    }
    
    /* Check whether the current action is in this object's action list. */
    actionInList()
    {
        foreach (local cur in action)
        {
            if (gAction.ofKind(cur))
                return true;
        }
        return nil;
    }
    
    /* 
     *   If this property is set to true, the action will be stopped after 
     *   this object has performed its reaction.
     */
    stopAction = nil
    
    /* 
     *   Assuming we're active, react. This method should not be overridden 
     *   by authors, as it does a little housekeeping.
     */
    exec()
    {
        /* 
         *   If we inherit from Script, run the script; otherwise let the 
         *   react() method handle it. Stop the action afterwards if 
         *   necessary.
         */
        if (ofKind(Script))
            doScript();
        else
            react();
        
        /* If this is a one-off reaction, note that it's done. */
        if (isOneOff)
            isDone = true;
            
        /* If this reaction stops the action, stop it now. */
        if (stopAction)
            exit;
    }
    
    /* A method for authors to override - called by exec(). */
    react() 
    {
        /* by default, do nothing */
    }
    
    /* 
     *   We may want some objects to react before others, so a priority 
     *   property. The lower the priority, the sooner this object will be 
     *   called. The default is 100, for no reason other than to be consistent
     *   with adv3's handling of similar properties.
     */
    priority = 100
    
    /* 
     *   For consistency with adv3 classes, a method to return the actor to 
     *   which this ReAction object belongs.
     */
    getActor()
    {
        /* if nested in an ActorState or something similar */
        if (location.ofKind(ActorState))
            return location.getActor();
        
        /* otherwise just return whatever it's nested in */
        return location;
    }
;


/* The Reaction subclasses. */
class BeforeAction: Reaction
    reactionObjList = &beforeObjList
;

class AfterAction: Reaction
    reactionObjList = &afterObjList
;

class RoomBeforeAction: Reaction
    reactionObjList = &roomBeforeObjList
;

class RoomAfterAction: Reaction
    reactionObjList = &roomAfterObjList
;


/* 
 *   Preinitialisation must make sure each object in the game gets lists of 
 *   its BeforeAction and AfterAction objects.
 */
reactionObjsPreinit: PreinitObject
    /* 
     *   Run through all the Reaction objects in the game and place them in the
     *   correct list in their parent objects. Then sort the lists.
     */
    execute()
    {
        /* Add all objects descended from Reaction to their owners' lists. */
        forEachInstance(Reaction, new function(x) {
            local prop = x.reactionObjList();
            x.location.(prop) = x.location.(prop).append(x);
        });
        
        /* Sort the lists. */
        local func = {a, b: a.priority - b.priority};
        for (local obj = firstObj(Thing); obj != nil; 
                obj = nextObj(obj, Thing)) {
            sortLists(obj, func);
        }
        for (local obj = firstObj(ActorState); obj != nil; 
                obj = nextObj(obj, ActorState)) {
            sortLists(obj, func);
        }
    }
    
    sortLists(obj, func) 
    {
        /* All the lists that need to be sorted. */
        local lists = [&beforeObjList, &afterObjList];
        if (obj.ofKind(BasicLocation))
        {
            lists += [&roomBeforeObjList, &roomAfterObjList];
        }
        
        /* Sort each of the lists for the given object. */
        foreach (local l in lists)
        {
            if (obj.(l).length > 0)
                obj.(l) = obj.(l).sort(true, func);
        }
    }
    
    execBeforeMe = [adv3LibPreinit]
;


/* Now to teach objects how to use their brand-spanking new Reaction objects. */
modify Thing
    /* 
     *   The lists of BeforeAction or AfterAction objects. None by default, 
     *   of course.
     */
    beforeObjList = []
    afterObjList = []
    
    /* 
     *   The beforeAction method must check whether this object has any 
     *   BeforeAction objects, and if so, whether any of them are active. Tell
     *   any active ones to react.
     */
    beforeAction() 
    {
        foreach (local obj in beforeObjList) {
            if (obj.isActive)
                obj.exec();
        }
    }
    
    /* Same deal for afterAction. */
    afterAction() 
    {
        foreach (local obj in afterObjList) {
            if (obj.isActive)
                obj.exec();
        }
    }
;

modify BasicLocation
    /* The lists of RoomBeforeAction and RoomAfterAction objects. */
    roomBeforeObjList = []
    roomAfterObjList = []
    
    /* 
     *   The roomBeforeAction method must check whether this object has any
     *   RoomBeforeAction objects, and if so, whether any of them are active.
     *   Tell any active ones to react.
     */
    roomBeforeAction()
    {
        foreach (local obj in roomBeforeObjList) {
            if (obj.isActive)
                obj.exec();
        }
    }
    
    /* Same deal for roomAfterAction. */
    roomAfterAction()
    {
        foreach (local obj in roomAfterObjList) {
            if (obj.isActive)
                obj.exec();
        }
    }
;


/* 
 *   Actors have special beforeAction handling, which we need to preserve 
 *   while still including the default Thing handling. Rather arbitrarily, 
 *   we'll let the Actor handling (including checking the current 
 *   ActorState's handling) run before the Thing handling.
 */
modify Actor
    beforeAction() 
    {
        inherited();
        inherited Thing();
    }
    
    afterAction() 
    {
        inherited();
        inherited Thing();
    }
;


/* 
 *   ActorStates should treat beforeAction and afterAction the same way Things
 *   do.
 */
modify ActorState
    /* 
     *   The lists of BeforeAction or AfterAction objects. None by default, 
     *   of course.
     */
    beforeObjList = []
    afterObjList = []
    
    /* 
     *   Delegate beforeAction and afterAction to the equivalent methods of 
     *   Thing.
     */
    beforeAction() 
    {
        delegated Thing();
    }
    
    afterAction() 
    {
        delegated Thing();
    }
;


/* 
 *   Keyrings have special beforeAction and afterAction handling, which we 
 *   need to preserve while still including the default Thing handling.
 */
modify Keyring
    beforeAction() 
    {
        inherited();
        inherited Thing();
    }
    
    afterAction() 
    {
        inherited();
        inherited Thing();
    }
;
