/*
	VERSION:		1.9
	1.9		Change:  commands are run synchronously unless a promise is returned
	1.8		Fix:  output_prom is locally scoped by accident
	1.7		Change: 	runEvent()  now returns the promise for the current/new script.
	1.6		Added:  _this.promise,  which is created each time the script starts,  and resolves each time the script completes
	1.5		Now uses avoidLoopPanic() to (hopefully) prevent very long scripts from freezing Flash.
	1.4		added abort() to the resulting array, which can be called manually to abort the script
	1.3		done() only fires one time per script-run.  done() can be safely evoked even if eventIndex is far beyond the length.
	1.2		Added "onScriptDone" event
	1.1		Added an option to define checkForInterrupt() that can interrupt the script when nextEvent() is called
	
	ABOUT THIS:
		This is a modified version of scriptSystem.as version 1.5
		It stores an array of objects, each containing:
			run()				This function is called when this command is run
			command			This object contains command data, and is passed to the run() function.
			baseScript	This object contains the root script.  It's used by commands to set the "timeoutID"
	
	PURPOSE:
		This runs blocks of code in sequence, as a chain-reaction.
		allowing for arbitrary delays and halts.
		This is simple but very powerful.
		
	USAGE:
		talk = makeScriptSystem();
		talk[0] = {
			run: function( self_obj ){},
			myVar1: 42,
			myVar2: "merf"
		}
		talk.runEvent(0);
		talk.done = function(){}
		// automatically used by the script to determine whether or not it's allowed to continue after each command
		talk.checkForInterrupt = function(){
			// if sprite doesn't exist
			return false;		// prevents this script from running
		}
		
	NOTE:
		A standard set of event functions can be defined,
		which makes it easier to use this because 
		they can automatically call nextEvent() afterwords.
		example:
			events.push(		talk( events, "Hello World!");		);
		A reference to the script system is passed to them,
		allowing them to resume the script when they're done.
		
	FUNCTIONS:
		done()				Externally-defined function that's called when the script reaches its end.
		
	EVENTS:
		onScriptDone	Fires after the script has finished executing
		
	PROPERTIES:
		isDone				Indicates whether the script has already finished running.		(null=never ran  false=running  true=complete)
*/
#include "avoidLoopPanic.as"
#include "functions/VOW.as"
if(!_global.VOW)		_global.VOW = VOW;
makeScriptSystem = function()
{
	var parentSprite_mc = _this;
	// create container
	var _this = [];
	
	// add events
	AsBroadcaster.initialize(_this);
	
	// variables
	var script_vow = null;					// promise controller  				(created upon starting, kept upon finishing / aborting)
	_this.promise = null;		// current run-time promise		(created upon starting, kept upon finishing / aborting)
	_this.index = 0;				// current event index
	_this.isDone = null;		// script has never been run
	var script_uid = Math.floor( Math.random() * 99 );
	
	// prevent Flash from panicking when a ton of script-commands are called at once
	if(!_global.AVOID_LOOP_PANIC){
		_global.AVOID_LOOP_PANIC = make_avoidLoopPanic( 100 );
	}
	
	
	
	// ______________________________________________________________________________________________________
	// FUNCTIONS
	function done(){
		// avoid completing the same run twice
		if(_this.isDone === true)		return;
		_this.isDone = true;							// script IS done
		
		// prevent nextEvent() from being able to run anything, until this script is explicitly started again
		_this.index = _this.length;
		
		// announce that this script has just completed
		_this.done();										
		_this.broadcastMessage("onScriptDone");
		if( script_vow.getStatus() === "pending" )		script_vow.keep();		// complete previous script promise
		// return the current script-promise,  because runEvent() normally returns the script-promise, and will "return done()" when a script is done/interrupted
		return script_vow.promise;
	}// done()
	_this.abort = done;
	
	
	
	// run script commands synchronously until a command returns a pending promise.  Wait for each promise before resuming.  Promises that resolve to "false" or erroneous "undefined" output values will interrupt the script.
	_this.runEvent = function( startIndex ){
		// remember where you are in the list
		_this.index = startIndex || 0;
		// end of script => done
		if(_this.index >= _this.length)		return done();
		
		if( TRACE_SCRIPT ){
			var commandType = _this[_this.index].command_obj.data.type;
			trace("runEvent( " + _this.index + " )  " + commandType );
			trace("  _this[" + _this.index + "]: " + _this[_this.index]);
		}
		
		// reset the script's promise when starting from the beginning
		var newRun = (_this.index === 0);
		if( newRun ){
			// complete the previous script-promise  (without announcing it?)
			if( script_vow.getStatus() === "pending" )		script_vow.keep();
			// make a new script-promise
			script_vow = VOW.make();
			_this.promise = script_vow.promise;
		}// if:  starting this script from the beginning
		
		
		_this.isDone = false;							// script isn't done
		var isSynchronous = true;
		var interruptScript = false;
		// synchronously run commands until a command's promise is pending
		do{
			commandType = _this[_this.index].command_obj.data.type;
			// end of script => done
			if(_this.index >= _this.length)		return done();
			// 
			var commandIsDone_prom = _this[_this.index]();		// run this command and store its output-promise
			var promiseStatus = commandIsDone_prom.getStatus();
			// if this promise is already kept,  then immediately resume the script, synchronously
			isSynchronous = (promiseStatus === "kept");
			// check for interruptions  (promise is already broken,  script is already aborted,  or rpgSprite wants to abort)
			if(promiseStatus === "broken")	interruptScript = true;		// if this promise is already broken,  then abort the script
			if( _this.isDone )							interruptScript = true;		// if any command called abort(), then the "isDone" flag will become true.  calling done() again will be harmless.
			if( _this.checkForInterrupt() === false )		interruptScript = true;
			if( !commandIsDone_prom.is_promise )		interruptScript = true;		// the returned value should always be a promise.  If it is not, then abort()
			// 
			var returnDesc = commandIsDone_prom;
			if( returnDesc.is_promise )		returnDesc = "Promise object";
			if( promiseStatus )
				returnDesc += " (" + promiseStatus + ")";
			returnDesc += " (isSynchronous: " + isSynchronous + ")";
			returnDesc += " (interruptScript: " + interruptScript + ")";
			if( TRACE_SCRIPT )		trace("  " + commandType + " returned: " + returnDesc);
			// if synchronous,  then increment the command index
			if( isSynchronous && interruptScript === false )		_this.index++;
		}while( isSynchronous && interruptScript === false );
		
		
		// unload  =>  break every pending promise
		if( isSynchronous === false ){
			var react_to_unload = react.once().to("unload");
			react_to_unload.then = function(){
				if( commandIsDone_prom.getStatus() === "pending" ){
					commandIsDone_prom.doBreak();
				}
			}// react_to_unload()
			commandIsDone_prom.then = function(){
				react_to_unload.disable();
			};// commandIsDone_prom()
		}// if waiting
		
		
		// asynchronously wait for this command's promise,  then resume running commands synchronously
		if( interruptScript === false  &&  isSynchronous === false )
		{// if:  a promise is pending
			commandIsDone_prom.then( _this.nextEvent, done );
		}// if:  a proise is pending
		
		// if:  this script is interrupted,  then this script stops and is immediately complete
		// calling done() twice is harmless
		if( interruptScript )		return done();
		
		// return the current script-promise
		return _this.promise;
	}// runEvent()
	
	
	
	// checkForInterrupt(),  runEvent() with next index
	_this.nextEvent = function(){
		var allowRun = (_this.checkForInterrupt) ? _this.checkForInterrupt() : true;
		if( allowRun === false )		done();
		
		// else
		_this.runEvent(_this.index+1);		// this function also updates "index"
	}// nextEvent()
	// ______________________________________________________________________________________________________
	
	
	
	// return container
	return _this;
}// makeScriptSystem()