/*		VERSION:		2.3
2.3		FIX: 		monad.fire() tell handling was accidentally overwriting the evt.type of the original event instead of the tell event,  blocking previous react.to reactions
2.2		FIX: 		Automatic unload does call unload reactions.
			FIX: 		Automatic unload handles unload events that call the unload event (infinite loop)  by unlistening + forgetting reactions before running them. Then it erases them.
2.1		FIX: 		The automatic unload cleanup no longer manually fires "unload" reactions
2.0		complete rewrite to refactor and simplify the code
			tell() can be used multiple times within a single chain
			until() will be re-used each time then() gets assigned
			More efficient:  setting then() to undefined automatically un-listens, if there's no tellTarget.  Accomplish this by making make then() a property.
			Bugfix:  apparently auto-unload wasn't calling the "unload" reactions in ver 1.7
			auto-cleanup is now allowed to happen more than once.  This seems unlikely, but this clean-up is expected behavior that should not unexpectedly vanish.
1.7		added until()
1.6		FIX: 		tell() was disabling all previous then() reactions
1.5		"unload" events always fire only once
			react.unload() always fires all "unload" events that were created using react
1.4		changed	tell()  to allow re-tranmitting an event using a different event name			react.to("click").tell(mc, "omgEvent")
1.3		added once()
1.2		added tell()

USAGE:
	_this = this;
	#include "functions/eventSystem3.as"
	if(!_this.addListener)		AsBroadcaster.initialize( _this );
	if(!react)								var react = make_react( _this );		// param is optional
	function onUnload(){
		sendEvent( "unload" );		// this will trigger react.unload()		... and trigger any external code listening for "unload" to occur here
	}// onUnload()
	
	
	react.to("click").from(mc).then = function(evt){}
	
	
	react.to("testEvent").tell(button1).then = 
	react.to("click").from(button1).tell(button2, "button1_click").then = 
	
	
	react.once().to("testEvent").then = 
	react.to("testEvent").once().then = 
	react.to("testEvent").from(button1).once().then = 
	
	
	var detectClick = react.to("click").from(_this);
	detectClick.then = function(evt){}
	detectClick.disable();
*/
#include "sendEvent.as"
function make_react( defaultUnloadEmitter )
{
	var react = {};					// output of make_react()
	var allReactions = [];	// remember every reaction created with this "react" object, for auto-cleanup
	
	
	
	//////////////////////////////////////////
	react.to = function( eventName, settings ){
		// create a reaction-monad
		var reaction = newReaction( eventName, settings );
		var monad = reaction.monad;
		// remember this reaction
		allReactions.push( reaction );
		// return this reaction-monad
		return monad;
	}// react.to()
	
	
	
	//////////////////////////////////////////
	// react.once()
	
	var reactOnce = {};
	reactOnce.to = function( eventName, settings ){
		if( !settings )		var settings = {};
		settings.fireOnce = true;
		return react.to( eventName, settings );
	}// reactOnce.to()
	
	react.once = function(){
		return reactOnce;		// make this a chain-able monad
	}// react.once()
	
	
	
	//////////////////////////////////////////
	// create reaction object, which contains all internal settings + external monad and functions
	function newReaction( eventName, newSettings ){
		// create output object
		var reaction = {};
		
		// settings  (many of these could be closures, but I chose to make all of them stored variables for the sake of consistency)
		reaction.uid = Math.floor( Math.random() * 99 );
		reaction.hasListener = false;							// This flag tracks whether a listener is attached for this event reaction
		reaction.eventName = eventName;						// "click"  The name of the event this is going to react to
		reaction.emitter = defaultUnloadEmitter;	// An object or MovieClip where this event will occur.  We'll react to this event occuring within this object.
		reaction.fireOnce = false;								// A flag that controls whether this reaction automatically disables itself after it reacts to an event  (sets monad.then to (undefined) after reacting)
		reaction.eventTarget = {};								// An object for standard listeners that will "receive" the event.  It contains a function named after the event which IS monad.fire()
		reaction.eventTarget[reaction.eventName];	// This function is monad.fire()
		reaction.untilTimeout = undefined;				// store the duration of until() if it was used, so that then() can re-apply until() again if this() is later re-assigned
		reaction.untilId = undefined;							// setTimeout ID used to clear pending setTimeout's created by until()
		reaction.tellThese = [];									// Other objects to send this event to when it occurs + what to call it in each of the announcements
			// eventTarget													// An additional object to send this event to when it occurs
			// eventName														// What to call the event when sending it to the additional object
		
		
		// apply all override-settings passed into this function  (currently only used to override "fireOnce")
		for(var nam in newSettings){
			reaction[ nam ] = newSettings[ nam ];
		}// for each: newSettings
		
		
		// create monad.  This is the interface for external code.  It contains: cleanup() disable() then() fire() tell() once() until() from() forget()
		var monad = reaction.monad = {};
		
		
		// define the monad's interface
		
		// define monad.then() property.  then = (undefined) initially.  External code will assign a function to this later.
		function get_then(){
			// recall the assigned function
			return reaction.then;
		}// get_then()
		function set_then( new_func ){
			// remember the assigned function
			reaction.then = new_func;
			// listen or unlisten for this reaction's eventName, therefore enabling or disabling this reaction
			var isFunc = new_func instanceof Function;
			var hasTells = (reaction.tellThese.length > 0);
			// if:  has value  OR  has tell()'s defined	=>  (ON) listen
			if( isFunc || hasTells ){
				listen();
				// re-apply the until() timeout
				if( reaction.untilTimeout !== undefined )		monad.until( reaction.untilTimeout );
			}
			// if:  no-value  AND  no tell()'s defined		=>  (OFF) un-listen
			else{
				unListen();
				clearTimeout( reaction.untilId );
			}
		}// set_then()
		monad.addProperty("then", get_then, set_then);
		// initialize to undefined
		monad.then = undefined;
		
		
		// monad.cleanup()
		// intended for auto-cleanup, but can also be used manually.
		// this reaction can theoretically be re-used afterwards, but it's not supposed to be.  all previous tell's will not respond because they're erased.  until() will be re-applied when then is assigned.
		monad.unListen = unListen;
		monad.cleanup = function(){
			// forget tell's
			// reaction.tellThese = [];
			for(var t in reaction.tellThese)		reaction.tellThese.splice( t, 1 );
			// un-assign "then" reaction
			monad.then = undefined;
			// detach listener,  even if tells exist
			// unListen();
			// don't call monad.forget() here because the auto-cleanup can do that more directly
		}// cleanup()
		
		
		// define monad.disable()		(Legacy disable-and-forget)
		// This is a legacy function and is not internally used by this version,  but IS externally used by the RPG script system  (in rpgSprite,  near lines 315, 694, 1087, 1147)
		monad.disable = function(){
			monad.cleanup();
			monad.forget();
		}// monad.disable()
		
		
		// define monad.fire()  This maps to the standard listener object's event-function, and receives the all parameters of a standard event
		// Calling this triggers this reaction.  This gets called each time the listener responds, and during auto clean-up
		monad.fire = function(){
			// detect deleted property + direct reassignment		// repair the 'then' property
			if( monad.then !== reaction.then ){
				trace("WARNING:  'then' property was deleted. Now restoring it with the current reaction.");
				var newestThen = monad.then;
				monad.addProperty("then", get_then, set_then);
				monad.then = newestThen;
			}
			// call then() while passing all the current standard event parameters to it
			var evt_array = arguments.slice();
			var evt = evt_array[0];
			monad.then.apply( null, evt_array );
			// also send events to any additional tell() objects
			for(var t=0; t<reaction.tellThese.length; t++){
				if( !reaction.tellThese[t].eventTarget )		continue;
				if( !reaction.tellThese[t].eventName )			continue;
				// carry-over the same evt data,  but change the cloned evt's "type" to be the new event-name
				var evt_clone = {};
				for(var nam in evt)		evt_clone[ nam ] = evt[ nam ];
				// evt.type = reaction.tellEventName;
				evt_clone.type = reaction.tellEventName;
				// send the event
				sendEvent( reaction.tellThese[t].eventName, evt_clone, reaction.tellThese[t].eventTarget );
			}// for each: additional tell() target
			
			// if "fireOnce" then set then() to (undefined)  (automatically un-listen)
			if( reaction.fireOnce === true )		monad.then = undefined;
		}// monad.fire()
		
		
		// listener calls monad.fire()
		// reaction.eventTarget never changes.  reaction.eventName never changes.  reaction.emitter CAN change.  listen() and unListen() will simply attach or detach a listener to this same reaction.eventTarget regardless of which emitter is used.
		reaction.eventTarget[ reaction.eventName ] = monad.fire;
		
		
		// define monad.tell()
		// adds an additional eventTarget + eventName to a list of events to be fired later when this reaction occurs
		monad.tell = function( newEventTarget, newEventName ){
			// require "newEventTarget"
			if( !newEventTarget )		return monad;
			// resolve "newEventName"
			if( newEventName instanceof Object ){
				newEventName = newEventName.about  ||  newEventName.to;
			}
			if( !newEventName )		var newEventName = reaction.eventName;
			// Bundle together a unique "eventTarget" and "eventName"
			var newTell = {
				eventTarget: newEventTarget, 
				eventName: newEventName
			};
			// remember these
			reaction.tellThese.push( newTell );
			// Now there's a reason to listen
			listen();
			// return the monad
			return monad;
		}// monad.tell()
		
		
		// define monad.once()
		// Tells this reaction to disable itself after triggering  (by setting "then" to undefined)
		monad.once = function(){
			reaction.fireOnce = true;
			return monad;
		}// monad.once()
		
		
		// define monad.until()
		// wait for this many milliseconds,  and afterwords disable this reaction by setting the then() property to (undefined)
		// if then() is later re-assigned,  until() will be re-applied and the reaction will automatically time-out after the original duration elases again
		monad.until = function( wait_ms ){
			// require "wait_ms" to be a number
			var isNumber = !isNaN( wait_ms );
			if( isNumber === false )		return monad;
			// wait for this many milliseconds,  and afterwords disable this reaction by setting the then() property to (undefined).  The current reaction can be re-activated by assigning another function to then() later.
			clearTimeout( reaction.untilId );
			reaction.untilId = setTimeout(function(){
				monad.then = undefined;
			}, wait_ms);
			// remember that until() was set,  so that re-assigning then() later will re-apply this
			reaction.untilTimeout = wait_ms;
			// return monad
			return monad;
		}// monad.until()
		
		
		// define monad.from()
		// swaps emitters, and re-attaches the listener if necessary
		monad.from = function( newEmitter ){
			// sanity check
			var validEmitter = ( newEmitter instanceof MovieClip )  ||  ( newEmitter instanceof Object );
			if( validEmitter === false )		return monad;
			var wasListening = reaction.hasListener;
			// stop listening to the previous emitter
			if( wasListening )		unListen();
			// set the intended emitter to the newly specified object
			reaction.emitter = newEmitter;
			// listen to the new emitter
			if( wasListening )		listen();
			// return monad
			return monad;
		}// monad.from()
		
		
		// define monad.forget()
		// removes this reaction from [allReactions] to exclude it from automatic clean-up later
		// this is normally only used by the "unload" automatic clean-up reaction to prevent it from endlessly calling itself, and prevent it from disabling itself
		// this is also used by the legacy monad.disable() function
		monad.forget = function(){
			// forget this one reaction
			for(var i in allReactions){
				// not found => check next item
				if( allReactions[i] !== reaction )		continue;
				// found => remove item
				allReactions.splice( i, 1 );
			}// find: this reaction within allReactions
			// return monad
			return monad;
		}// monad.forget()
		
		
		// attach listener
		function listen(){
			// if already listening,  then do nothing  (don't listen twice)
			if( reaction.hasListener === true )		return null;
			// if missing emitter,  then do nothing  (nothing to listen TO)
			if( reaction.emitter === undefined )	return null;
			// listen
			if(reaction.emitter.addEventListener){
				// Flash component
				reaction.emitter.addEventListener( reaction.eventName, reaction.eventTarget );
			}else if(reaction.emitter.addListener){
				// Flash AsBroadcaster
				reaction.emitter.addListener( reaction.eventTarget );
			}else{
				// cannot create event,  so return nothing
				var nam = (reaction.emitter._name === undefined) ? "[object]" : '"'+reaction.emitter._name+'"';
				trace('ERROR:  '+nam+' cannot send events. ('+reaction.eventName+')');
				return null;
			}
			// remember whether or not we're listening
			reaction.hasListener = true;
		}// listen()
		
		
		// remove listener
		function unListen(){
			// if already not listening,  then do nothing  (nothing needs to be done)
			if( reaction.hasListener === false )		return null;
			// if missing emitter,  then do nothing  (nothing to stop listening to)
			if( reaction.emitter === undefined )		return null;
			// un-listen
			if(reaction.emitter.removeEventListener){
				// Flash component
				reaction.emitter.removeEventListener( reaction.eventName, reaction.eventTarget );
			}else{
				// Flash AsBroadcaster
				reaction.emitter.removeListener( reaction.eventTarget );
			}
			// remember whether or not we're listening
			reaction.hasListener = false;
		}// unListen()
		
		
		// output:  reaction "settings" object
		return reaction;
	}// newReaction()
	
	
	
	//////////////////////////////////////////
	// self-cleanup
	react.unload = function( fireUnloadEvents ){
		// resolve "fireUnloadEvents"
		if( fireUnloadEvents === undefined )		fireUnloadEvents = true;		// true unless told otherwise
		
		// unlisten, forget, (fire), erase all reactions  (only "unload" reactions get fired-off)
		for(var r in allReactions){
			// stop reacting  (so it cannot trigger itself)
			allReactions[r].monad.unListen();
			// forget this reaction  (so it cannot trigger itself by repeating this auto-cleanup)
			// forget every event reaction  (this is faster then putting forget() inside cleanup() because it won't need to search for the reaction this way)
			allReactions.splice( r, 1 );
			// if this is an "unload" reaction,  then run it
			if( fireUnloadEvents && allReactions[r].eventName === "unload" )		allReactions[r].monad.fire();
			// erase this reaction's behavior
			allReactions[r].monad.cleanup();
		}// for each: allReactions
	}// unload()
	
	
	
	//////////////////////////////////////////
	// make self-cleanup automatic
	if( defaultUnloadEmitter ){
		react.to("unload").forget().then = function( ){
			react.unload( true );
		} // upon unload event()
	}// if:  defaultUnloadEmitter is specified
	
	
	//////////////////////////////////////////
	// output of make_react()
	return react;
}// make_react()