/*
 * Boomerang - ShowController.js
 * Copyright (c) 2009 Apple, Inc.
 * All rights reserved.
 *
 * Responsibility: jdmack
 */

// Required Files:
// ===============

// Class: ShowController
// ===============================================================

var kDebugShowController							= "ShowController";

// States:
// -------

var kShowControllerState_Stopped					= "Stopped";
var kShowControllerState_Starting					= "Starting";

var kShowControllerState_DownloadingScript			= "DownloadingScipt";
var kShowControllerState_SettingUpScene				= "SettingUpScene";

var kShowControllerState_IdleAtFinalState			= "IdleAtFinalState";
var kShowControllerState_IdleAtInitialState			= "IdleAtInitialState";

var kShowControllerState_WaitingToJump				= "WaitingToJump";
var kShowControllerState_ReadyToJump				= "ReadyToJump";

var kShowControllerState_WaitingToDisplay			= "WaitingToDisplay";
var kShowControllerState_ReadyToDisplay				= "ReadyToDisplay";

var kShowControllerState_WaitingToPlay				= "WaitingToPlay";
var kShowControllerState_ReadyToPlay				= "ReadyToPlay";

var kShowControllerState_Playing					= "Playing";

// Events:
// -------

var kKeyDownEvent									= "keydown";

var ShowController = Class.create

({
	initialize : function()
	{
		var kDebugInitialize = "initialize";

		this.extractDelegateFromUrlParameter();
		
		// TODO: investigate moving this later on in the process
		// - it was in displayScene, which only happens after the KPF file is downloaded and the first scene is ready to go
		// - but there was some mysterious issue with it not initially displaying in the iframe inside heckler
		
		this.delegate.showDidLoad();
		
		this.showUrl						= getUrlParameter( "showUrl" );
		
		debugMessage( kDebugShowController, kDebugInitialize, "showUrl: " + this.showUrl );
		
		// These must be created before the OrientationController as they subscribe to its event
		
		this.displayManager					= new DisplayManager();
		this.scriptManager					= new ScriptManager( this.showUrl );
		this.textureManager					= new TextureManager( this.showUrl );
		this.stageManager					= new StageManager( this.textureManager, this.scriptManager );
		this.touchController				= new TouchController();
		this.animationManager				= new AnimationManager();
		
		this.orientationController			= new OrientationController();

		this.activeHyperlinks				= null;
		
		this.script							= null;
		
		this.currentSceneIndex				= -1;
		this.nextSceneIndex					= -1;
		
		this.sceneToJumpTo					= -1;
		this.sceneToDisplay					= -1;
		
		this.currentSlideIndex				= -1;
		this.previousSlideIndex				= -1;
		
		this.currentSoundTrackIndex			= 0;
		
		this.transformOriginValue			= "";

		this.accumulatingDigits				= false;
		this.digitAccumulator				= 0;
		
		this.firstSlide						= true;
		
		this.embedMode						= kEmbedModeNonEmbedded;		
		this.accountID						= "";		
		this.guid							= "";		
		this.locale							= "EN";		
		this.isNavigationBarVisible			= false;		
		this.isFullscreen					= false;		
		this.volume							= 3.0;		
		this.muted							= false;

		this.soundTrackPlayer				= null;
		
		// Hookup event handlers to listen for events from helper classes
		
		var self = this;
		
		document.observe( kStageIsReadyEvent, 			function( event ) { self.handleStageIsReadyEvent( event ); }, false );
		
		document.observe( kScriptDidDownloadEvent,		function( event ) { self.handleScriptDidDownloadEvent( event ); }, false );
		document.observe( kScriptDidNotDownloadEvent,	function( event ) { self.handleScriptDidNotDownloadEvent( event ); }, false );
		
		document.observe( kSceneDidPreloadEvent,		function( event ) { self.handleSceneDidPreloadEvent( event ); }, false );
		document.observe( kSceneDidNotPreloadEvent,		function( event ) { self.handleSceneDidNotPreloadEvent( event ); }, false );

		document.observe( kSwipeEvent,					function( event ) { self.handleSwipeEvent( event ); }, false );

		document.observe( kStageSizeDidChangeEvent, 	function( event ) { self.handleStageSizeDidChangeEvent( event ); }, false );

		document.observe( kKeyDownEvent,				function( event ) { self.handleKeyDownEvent( event ); }, false );
		
		// Can't use event observer for tap events
		// - this would cause the handler to be on a seperate event loop invocation
		// - this would prevent us from doing this like opening new tabs (popup blocker logic kicks in)
		
		this.touchController.registerTapEventCallback( function( event ) { self.handleTapEvent( event ); } );
		
		// Initialize our state...
		
		this.changeState( kShowControllerState_Stopped );
	},
	
	extractDelegateFromUrlParameter : function()
	{
		var kDebugExtractDelegateFromUrlParameter = "";//"extractDelegateFromUrlParameter";
		
		var delegateParameter = getUrlParameter( "delegate" );
		
		if( (delegateParameter == "")
		||  (delegateParameter == null)
		||  (typeof(delegateParameter) == "undefined") )
		{
			debugMessage( kDebugShowController, kDebugExtractDelegateFromUrlParameter, "no delegate parameter specified in URL, using null delegate" );
			
			this.delegate = new NullDelegate();
			
			return;
		}
		
		debugMessage( kDebugShowController, kDebugExtractDelegateFromUrlParameter, "delegate: " + delegateParameter );
		
		var indexOfDot = delegateParameter.indexOf( "." );
		
		this.delegate = window;
		
		while( indexOfDot != -1 )
		{
			var nextPart = delegateParameter.substring( 0, indexOfDot );
			
			debugMessage( kDebugShowController, kDebugExtractDelegateFromUrlParameter, "- nextPart: " + nextPart );
			
			this.delegate = this.delegate[nextPart];
			
			delegateParameter = delegateParameter.substring( indexOfDot + 1 );
			indexOfDot = delegateParameter.indexOf( "." );
		}

		debugMessage( kDebugShowController, kDebugExtractDelegateFromUrlParameter, "- lasePart: " + delegateParameter );

		this.delegate = this.delegate[delegateParameter];
	},
	
	// Heckler <-> Player Interface Methods
	// ------------------------------------
	
	setDelegate : function( hecklerDelegate )
	{
		var kDebugSetDelegate = "";//"setDelegate";
		
		debugMessage( kDebugShowController, kDebugSetDelegate, "delegate has been set" );
	},
	
	setProperty : function( propertyName, propertyValue )
	{
		var kDebugSetProperty = "";//"setProperty";
		
		debugMessage( kDebugShowController, kDebugSetProperty, "setting '" + propertyName + "' to: " + propertyValue );

		switch( propertyName )
		{
			case kPropertyName_embedMode:
				switch( propertyValue )
				{
					case kEmbedModeEmbedded:
					case kEmbedModeNonEmbedded:
						this.embedMode = propertyValue;
						break;
					default:
						debugMessage( kDebugShowController, kDebugSetProperty, "invalid value specified for " + kPropertyName_embedMode + ": " + propertyValue );
						break;
				}
				break;

			case kPropertyName_presentationName:
				// TODO: determine if we need setProperty(presentationName)
				break;

			case kPropertyName_accountID:
				this.accountID = propertyValue;
				break;

			case kPropertyName_guid:
				this.guid = propertyValue;
				break;

			case kPropertyName_locale:
				this.locale = propertyValue;
				break;

			case kPropertyName_currentSlide:
				this.jumpToSlide( propertyValue );
				break;

			case kPropertyName_isNavigationBarVisible:
				this.isNavigationBarVisible	= propertyValue;		
				break;

			case kPropertyName_isFullscreen:
				break;

			case kPropertyName_volume:
				break;

			default:
				debugMessage( kDebugShowController, kDebugSetProperty, "- unknown property name" );
				break;
		}
	},
	
	getProperty : function( propertyName )
	{
		var kDebugGetProperty = "";//"getProperty";
		
		debugMessage( kDebugShowController, kDebugGetProperty );

		var propertyValue = null;
		
		switch( propertyName )
		{
			case kPropertyName_embedMode:
				propertyValue = this.embedMode;
				break;

			case kPropertyName_presentationName:
				propertyValue = this.showUrl;
				break;

			case kPropertyName_accountID:
				propertyValue = null;
				break;

			case kPropertyName_guid:
				propertyValue = null;
				break;

			case kPropertyName_locale:
				propertyValue = null;
				break;

			case kPropertyName_currentSlide:
				propertyValue = this.currentSlideIndex + 1;
				break;

			case kPropertyName_isNavigationBarVisible:
				propertyValue = null;
				break;

			case kPropertyName_isFullscreen:
				propertyValue = null;
				break;

			case kPropertyName_volume:
				propertyValue = null;
				break;

			default:
				debugMessage( kDebugShowController, kDebugSetProperty, "unknown property name" );
				break;
		}

		debugMessage( kDebugShowController, kDebugGetProperty, "getting '" + propertyName + "'... returning '" + propertyValue + "'" );
		
		return propertyValue;
	},
	
	gotoSlide : function( slideNumber )
	{
		var kDebugGotoSlide = "";//"gotoSlide";
		
		debugMessage( kDebugShowController, kDebugGotoSlide, "slideNumber: " + slideNumber );
		
		this.jumpToSlide( slideNumber );
	},
	
	pause : function()
	{
		var kDebugPause = "";//"pause";
		
		debugMessage( kDebugShowController, kDebugPause, "currently not implemented!"  );
	},
	
	play : function()
	{
		var kDebugPlay = "";//"play";
		
		debugMessage( kDebugShowController, kDebugPlay, "currently not implemented!"  );
	},
	
/*debug*/	debugDiagnosticDump : function()
/*debug*/	{
/*debug*/		var kDebugDiagnosticsDump = "debugDiagnosticDump";
/*debug*/				
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "*****************************************************" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "***  S T A R T   D I A G N O S T I C   D U M P   ****" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "*****************************************************" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-               state: " + this.state );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-           numSlides: " + this.script.slideCount );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-           numScenes: " + this.script.numScenes );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-   currentSceneIndex: " + this.currentSceneIndex );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-      nextSceneIndex: " + this.nextSceneIndex );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-   currentSlideIndex: " + this.currentSlideIndex );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-  previousSlideIndex: " + this.previousSlideIndex );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-       loopSlideshow: " + (this.script.loopSlideshow ? "yes" : "no") );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-      hyperlinksOnly: " + (this.script.showMode == kShowModeHyperlinksOnly ? "yes" : "no") );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "-" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "- Pre-Built Texture Animations:" );
/*debug*/				
/*debug*/		for( var textureId in this.textureAnimations )
/*debug*/		{
/*debug*/			debugMessage( kDebugShowController, kDebugDiagnosticsDump, "- " + textureId );
/*debug*/		}
/*debug*/
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "- Active Hyperlinks:" );
/*debug*/
/*debug*/		var numHyperlinks = 0;
/*debug*/		var iHyperlink;
/*debug*/
/*debug*/		if( this.activeHyperlinks != null )
/*debug*/		{
/*debug*/			numHyperlinks = this.activeHyperlinks.length;
/*debug*/		}
/*debug*/
/*debug*/      if( numHyperlinks == 0 )
/*debug*/      {
/*debug*/          debugMessage( kDebugShowController, kDebugDiagnosticsDump, "- none" );
/*debug*/      }
/*debug*/
/*debug*/		for( iHyperlink = 0; iHyperlink < numHyperlinks; iHyperlink++ )
/*debug*/		{
/*debug*/			var hyperlink		= this.activeHyperlinks[iHyperlink];
/*debug*/
/*debug*/			debugMessage( kDebugShowController, kDebugDiagnosticsDump, "- " + iHyperlink + "::  x:" + hyperlink.targetRectangle.x + " y:" + hyperlink.targetRectangle.y + " w:" + hyperlink.targetRectangle.width + " h:" + hyperlink.targetRectangle.height + " url:" + hyperlink.url );
/*debug*/		}
/*debug*/
/*debug*/		this.textureManager.debugDumpCache();
/*debug*/
/*debug*/      this.stageManager.debugDumpTextures();
/*debug*/
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "*************************************************" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "***  E N D   D I A G N O S T I C   D U M P   ****" );
/*debug*/		debugMessage( kDebugShowController, kDebugDiagnosticsDump, "*************************************************" );
/*debug*/	},
	
	exitShow : function()
	{
		var kDebugExitShow = "exitShow";
		
		debugMessage( kDebugShowController, kDebugExitShow, "- exiting show..." );
		
		this.delegate.showExited();
	},
	
	onKeyPress : function( key, modifier )
	{
		var kDebugOnKeyPress = "";//"onKeyPress";

		debugMessage( kDebugShowController, kDebugOnKeyPress, "key: " + key + " modifier(s): " + (modifier.shiftKey ? "shift " : "" ) + (modifier.altKey ? "alt " : "" ) + (modifier.ctrlKey ? "ctrl " : "" ) + (modifier.metaKey ? "meta" : "" ) );

		if( (key >= kKeyCode_Numeric_0)
		&&  (key <= kKeyCode_Numeric_9) )
		{
			key = kKeyCode_0 + (key - kKeyCode_Numeric_0);
		}

		key += ( modifier.shiftKey	? kKeyModifier_Shift	: 0 );
		key += ( modifier.altKey	? kKeyModifier_Alt		: 0 );
		key += ( modifier.ctrlKey	? kKeyModifier_Ctrl		: 0 );
		key += ( modifier.metaKey	? kKeyModifier_Meta		: 0 );
		
		var digitEncountered = false;
		
		switch( key )
		{
			case kKeyCode_Escape:
				this.exitShow();
				break;

/*debug*/	//case kKeyCode_Return + kKeyModifier_Ctrl:
/*debug*/	//	this.showWaitingIndicator();
/*debug*/	//	break;

/*debug*/	case kKeyCode_Return + kKeyModifier_Meta:
/*debug*/		this.debugDiagnosticDump();
/*debug*/		break;
				
			case kKeyCode_Return:
				if( this.accumulatingDigits )
				{
					debugMessage( kDebugShowController, kDebugOnKeyPress, "- return pressed while accumulating digits, accumulator is now: " + this.digitAccumulator );
					this.accumulatingDigits	= false;

					if( this.script.showMode == kShowModeHyperlinksOnly )
					{
						debugMessage( kDebugShowController, kDebugOnKeyPress, "- can't do it, we're in hyperlinks only mode" );
					}
					else
					{
						if( this.digitAccumulator <= this.script.slideCount )
						{
							this.jumpToSlide( this.digitAccumulator );
						}
					}
					break;
				}
				else
				{
				}
				// fall through
				
			case kKeyCode_N:
			case kKeyCode_Space:
			case kKeyCode_RightArrow:
			case kKeyCode_RightArrow + kKeyModifier_Shift:
			case kKeyCode_PageDown:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- advance to next build..." );
				this.advanceToNextBuild();
				break;
				
			case kKeyCode_DownArrow + kKeyModifier_Shift:
			case kKeyCode_PageDown + kKeyModifier_Shift:
			case kKeyCode_CloseBracket:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- advance to next slide..." );
				this.advanceToNextBuild();
				break;
				
			case kKeyCode_LeftArrow + kKeyModifier_Shift:
			case kKeyCode_PageUp + kKeyModifier_Shift:
			case kKeyCode_OpenBracket:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- go back to previous build..." );
				this.goBackToPreviousBuild();
				break;

			case kKeyCode_P:
			case kKeyCode_Delete:
			case kKeyCode_PageUp:
			case kKeyCode_LeftArrow:
			case kKeyCode_UpArrow:
			case kKeyCode_UpArrow + kKeyModifier_Shift:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- go back to previous slide..." );
				this.goBackToPreviousSlide();
				break;

			case kKeyCode_Home:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- go back to first slide..." );
				if( this.script.showMode == kShowModeHyperlinksOnly )
				{
					debugMessage( kDebugShowController, kDebugOnKeyPress, "- can't do it, we're in hyperlinks only mode" );
				}
				else
				{
					this.jumpToSlide( 1 );
				}
				break;
				
			case kKeyCode_End:
				debugMessage( kDebugShowController, kDebugOnKeyPress, "- go back to last slide..." );
				if( this.script.showMode == kShowModeHyperlinksOnly )
				{
					debugMessage( kDebugShowController, kDebugOnKeyPress, "- can't do it, we're in hyperlinks only mode" );
				}
				else
				{
					this.jumpToSlide( this.script.slideCount );
				}
				break;
				
			default:
				if( (key >= kKeyCode_0) && (key <= kKeyCode_9) )
				{
					digitEncountered = true;
					if( this.accumulatingDigits == false )
					{
						debugMessage( kDebugShowController, kDebugOnKeyPress, "- digit entered, start accumulating digits..." );
						this.accumulatingDigits	= true;
						this.digitAccumulator	= 0;
					}
					debugMessage( kDebugShowController, kDebugOnKeyPress, "- accumulator was: " + this.digitAccumulator );
					this.digitAccumulator *= 10;
					this.digitAccumulator += (key - kKeyCode_0);
					debugMessage( kDebugShowController, kDebugOnKeyPress, "- accumulator now: " + this.digitAccumulator );
					
				}
				break;
		}
		if( this.accumulatingDigits && (digitEncountered == false) )
		{
			debugMessage( kDebugShowController, kDebugOnKeyPress, "- non-digit entered, stop accumulating digits..." );
			this.accumulatingDigits	= false;
			this.digitAccumulator	= 0;
		}
	},

	// DOM Event Handlers:
	// ===================

	handleKeyDownEvent : function( event )
	{
		var kDebugHandleKeyDownEvent = "";//"handleKeyDownEvent";
		
		var key = event.charCode || event.keyCode;
		
		var modifiers = 
		{
			altKey		: !!event.altKey,
			ctrlKey 	: !!event.ctrlKey,
			shiftKey	: !!event.shiftKey,
			metaKey		: !!event.metaKey
		};
		
		debugMessage( kDebugShowController, kDebugHandleKeyDownEvent, "keyCode: " + key );
		
		event.stopPropagation();
		this.onKeyPress( key, modifiers );	
	},
	
	handleClickEvent : function( event )
	{
		var kDebugHandleClickEvent = "";//"handleClickEvent";
		
		debugMessage( kDebugShowController, kDebugHandleClickEvent, "location: " + event.x + ", " + event.y);

		var displayCoOrds =
		{
			pointX : event.x,
			pointY : event.y
		};
		
		this.processClickOrTapAtDisplayCoOrds( displayCoOrds );
	},
	
	// Startup
	// =======
	
	incrementViewCount: function()
	{
		var kDebugIncrementViewCount = "";//"incrementViewCount";
		
		var showUrl = getUrlParameter( "showUrl" );
		
		// Obtain accountID and presentationName from showUrl
		// Match (p[0-9]{10}) for accountID
		// Match (.*\\.[^\/]*) for presentationName
		var params = showUrl.match('\/iw\/(p[0-9]{10})\/\/*(.*\\.[^\/]*)');
		if (params && params.length >= 3) {
			var accountID = params[1];
			// Strip out .iWork/Share/ from presentationName (if neccessary)
			var presentationName = params[2].replace(/.*\//,'');
			var viewCounterURL = ['/iw', accountID, '.Counters', encodeURIComponent(presentationName) + '?webdav-method=propget&counter'].join('/');
			
			debugMessage( kDebugShowController, kDebugIncrementViewCount, "issuing AJAX request to increment view count..." );

			new Ajax.Request( viewCounterURL, 
				{
					method: "get",
					onSuccess: function( transport ) {
						debugMessage( kDebugShowController, kDebugIncrementViewCount, "view count increment succeeded");
					},
					onFailure: function( transport ) {
						debugMessage( kDebugShowController, kDebugIncrementViewCount, "view count increment failed");
					}
			});
		}
	},

	startShow : function()
	{
		this.changeState( kShowControllerState_DownloadingScript );
		this.scriptManager.downloadScript();
		
		if (gMode == kModeMobile && gIsPublicViewer) {
  		this.incrementViewCount();
		}
	},
	
	// State Management
	// ================
	
	changeState : function( newState )
	{
		var kDebugChangeState = "changeState";

		if( newState != this.state )
		{
			debugMessage( kDebugShowController, kDebugChangeState, "leaving '" + this.state + "' entering '" + newState + "'" );

			this.accumulatingDigits	= false;
			this.digitAccumulator	= 0;

			this.leavingState();
			this.state = newState;
			this.enteringState();
		}
	},
	
	leavingState : function()
	{
		var kDebugLeavingState = "";//"leavingState";
		
		var debugMessageExtra = "";
		
		switch( this.state )
		{
			case kShowControllerState_Stopped:
				break;

			case kShowControllerState_Starting:
				break;

			case kShowControllerState_SettingUpScene:
				break;
				
			case kShowControllerState_IdleAtFinalState:
				break;
				
			case kShowControllerState_IdleAtInitialState:
				break;
				
			case kShowControllerState_WaitingToJump:
				break;

			case kShowControllerState_ReadyToJump:
				break;

			case kShowControllerState_WaitingToPlay:
				this.displayManager.hideWaitingIndicator();
				break;

			case kShowControllerState_ReadyToPlay:
				break;

			case kShowControllerState_Playing:
				break;
		}
		
		debugMessage( kDebugShowController, kDebugLeavingState, "leaving " + this.state + " currentSceneIndex: " + this.currentSceneIndex + " " + debugMessageExtra );
	},
	
	enteringState : function()
	{
		var kDebugEnteringState = "";//"enteringState";
		
		var debugMessageExtra = "";
		
		switch( this.state )
		{
			case kShowControllerState_Stopped:
				break;

			case kShowControllerState_Starting:
				this.showWaitingIndicator();
				break;

			case kShowControllerState_SettingUpScene:
				break;
				
			case kShowControllerState_IdleAtFinalState:
				this.updateSlideNumber();
				this.displayManager.hideWaitingIndicator();
				break;
				
			case kShowControllerState_IdleAtInitialState:
				this.updateSlideNumber();
				this.displayManager.hideWaitingIndicator();
				break;
				
			case kShowControllerState_WaitingToJump:
				this.showWaitingIndicator();
				break;

			case kShowControllerState_ReadyToJump:
				break;

			case kShowControllerState_WaitingToPlay:
				this.showWaitingIndicator();
				break;
				
			case kShowControllerState_ReadyToPlay:
				break;

			case kShowControllerState_Playing:
				break;
		}

		debugMessage( kDebugShowController, kDebugEnteringState, "entering " + this.state + " state, currentSceneIndex: " + this.currentSceneIndex + " " + debugMessageExtra );
	},

	updateSlideNumber : function()
	{
		var kDebugUpdateSlideNumber = "";//"updateSlideNumber";

		var adjustedSceneIndex = this.currentSceneIndex;
		
		debugMessage( kDebugShowController, kDebugUpdateSlideNumber, "this.currentSceneIndex: " + this.currentSceneIndex );
		
		if( this.state == kShowControllerState_IdleAtFinalState )
		{
			debugMessage( kDebugShowController, kDebugUpdateSlideNumber, "- but because we're waiting at end state, we need to add one..." );
			adjustedSceneIndex++;
		}
		
		var newSlideIndex = this.scriptManager.slideIndexFromSceneIndex( adjustedSceneIndex );
		
		if( this.firstSlide )
		{
			// TODO: see if we can get it to work here again
			// - we moved it back to "initialize()" (see comments there for reasoning)
//			this.delegate.showDidLoad();

			this.firstSlide = false;
		}
		
		debugMessage( kDebugShowController, kDebugUpdateSlideNumber, "- that makes slide index: " + newSlideIndex );
		
		if( this.currentSlideIndex != newSlideIndex )
		{
			debugMessage( kDebugShowController, kDebugUpdateSlideNumber, "- this differs from previous index of " + this.currentSlideIndex + ", update display and inform delegate..." );
			
			this.previousSlideIndex	= this.currentSlideIndex;
			this.currentSlideIndex	= newSlideIndex;
			
			this.displayManager.updateSlideNumber( this.currentSlideIndex + 1, this.script.slideCount );
			
			this.delegate.propertyChanged( kPropertyName_currentSlide, this.currentSlideIndex + 1 );
		}
		else
		{
			debugMessage( kDebugShowController, kDebugUpdateSlideNumber, "- this has not changed, nothing to do..." );
		}
	},

	showInfoPanel : function()
	{
		var kDebugShowInfoPanel = "";//"showInfoPanel";
		
		debugMessage( kDebugShowController, kDebugShowInfoPanel );
		
		this.displayManager.showInfoPanel();
		this.touchController.setTouchTrackingEnabled( false );
	},
	
	hideInfoPanel : function()
	{
		var kDebugHideInfoPanel ="";// "hideInfoPanel";
		
		debugMessage( kDebugShowController, kDebugHideInfoPanel, "hide the info panel and re-show the slides" );
		
		this.displayManager.hideInfoPanel();

		debugMessage( kDebugShowController, kDebugHideInfoPanel, "re-enable touch tracking" );
		
		this.touchController.setTouchTrackingEnabled( true );

		debugMessage( kDebugShowController, kDebugHideInfoPanel, "we're done" );
	},
	
	showWaitingIndicator : function()
	{
		var kDebugShowWaitingIndicator = "showWaitingIndicator";

		var currentTime					= new Date();
		var lastPreloadStart			= this.textureManager.lastPreloadStartTime();
		var timeSinceLastPreloadStart	= currentTime.getTime() - lastPreloadStart;
		
		if( timeSinceLastPreloadStart < 0.5 )
		{
			debugMessage( kDebugShowController, kDebugShowWaitingIndicator, "0.5s has not elapsed since last preload, don't show spinner" );
			return;
		}
		
		this.displayManager.showWaitingIndicator();
	},
	
	handleStageSizeDidChangeEvent : function( event )
	{
		var kDebugHandleStageSizeDidChangeEvent = "handleStageSizeDidChangeEvent";
		
		debugMessage( kDebugShowController, kDebugHandleStageSizeDidChangeEvent, "need to update TouchController with new track area" );
		
		this.touchController.setTrackArea( event.memo.left, event.memo.top, event.memo.width, event.memo.height );
	},
	
	handleTapEvent : function( event )
	{
		var kDebugHandleTapEvent = "";//"handleTapEvent";

		debugMessage( kDebugShowController, kDebugHandleTapEvent, "fingers: " + event.memo.fingers );

		var displayCoOrds =
		{
			pointX : event.memo.pointX,
			pointY : event.memo.pointY
		};
	
		this.processClickOrTapAtDisplayCoOrds( displayCoOrds );
	},
	
	processClickOrTapAtDisplayCoOrds : function( displayCoOrds )
	{
		var kDebugProcessClickOrTapAtDisplayCoOrds = "";//"processClickOrTapAtDisplayCoOrds";

		var isHyperlink = false;
		var hyperlinkUrl = "";
		
		if( this.displayManager.isInfoPanelShowing() )
		{
			debugMessage( kDebugShowController, kDebugProcessClickOrTapAtDisplayCoOrds, "info panel is showing, ignore clicks and taps" );
			return;
		}
		
		debugMessage( kDebugShowController, kDebugProcessClickOrTapAtDisplayCoOrds, "display location: (" + displayCoOrds.pointX + "," + displayCoOrds.pointY + ")" );

		var showCoOrds = this.displayManager.convertDisplayCoOrdsToShowCoOrds( displayCoOrds );

		debugMessage( kDebugShowController, kDebugProcessClickOrTapAtDisplayCoOrds, "- show location: (" + showCoOrds.pointX + "," + showCoOrds.pointY + ")" );

		if( showCoOrds.pointX == -1 )
		{
			debugMessage( kDebugShowController, kDebugProcessClickOrTapAtDisplayCoOrds, "- outside of show area" );
		}
		else
		{
			hyperlinkUrl = this.findHyperlinkAtCoOrds( showCoOrds );
			isHyperlink = (hyperlinkUrl != "");
		}
		
		if( isHyperlink )
		{
			debugMessage( kDebugShowController, kDebugProcessClickOrTapAtDisplayCoOrds, "- hyperlink invokded. url: " + hyperlinkUrl );
			this.processHyperlinkUrl( hyperlinkUrl );
		}
		else
		{
			this.advanceToNextBuild();
		}
	},
	
	processHyperlinkUrl : function( hyperlinkUrl )
	{
		var kDebugProcessHyperlinkUrl = "";//"processHyperlinkUrl";

		debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, hyperlinkUrl );
		
		if( hyperlinkUrl.indexOf( "?slide=" )  == 0  )
		{
			var slideNumberText = hyperlinkUrl.substring( 7 );

			debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a jump to slide " + slideNumberText );

			var newSlideIndex	= parseInt( slideNumberText );
				
			this.jumpToSlide( newSlideIndex );
		}
		else if( hyperlinkUrl.indexOf( "?action=retreat" ) == 0  )
		{
			if( this.previousSlideIndex != -1 ) 
			{
				debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a jump to the last viewed slide, which was " + this.previousSlideIndex + 1 );
				this.jumpToSlide( this.previousSlideIndex + 1 );
			}
			else
			{
				debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a jump to the last viewed slide, but this is the first slide we've displayed" );
			}
		}
		else if( hyperlinkUrl.indexOf( "?id=" ) == 0  )
		{
			var slideNumberText = hyperlinkUrl.substring( 4 );

			debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a jump to a slide number: " + slideNumberText );
		}
		else if( hyperlinkUrl.indexOf( "http:" ) == 0 )
		{
			debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a jump to a web page: " + hyperlinkUrl );

			window.open( hyperlinkUrl, "_blank", null );
		}
		else if( hyperlinkUrl.indexOf( "playMovie:" ) == 0 )
		{
			var indexOfColon	= hyperlinkUrl.indexOf( ":" );
			var movieObjectId	= hyperlinkUrl.substring( indexOfColon + 1 );
			
			debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's a playMovie directive, movieObjectId: '" + movieObjectId + "'" );
			
			this.startMoviePlaying( movieObjectId );
		}
		else
		{
			debugMessage( kDebugShowController, kDebugProcessHyperlinkUrl, "- it's an unknown hyperlink type" );
		}
	},
	
	addHyperlink : function( targetRectangle, url )
	{
		var newHyperlink = { targetRectangle : targetRectangle, url : url };
		
		this.activeHyperlinks.push( newHyperlink );
	},
	
	clearAllHyperlinks : function()
	{
		var kDebugClearAllHyperlinks = "";//"clearAllHyperlinks";

		debugMessage( kDebugShowController, kDebugClearAllHyperlinks, "deleting old hyperlinks array and creating new one..." );
		
		this.stageManager.clearAllHyperlinks();
		
		delete this.activeHyperlinks;
		this.activeHyperlinks = new Array();
	},
	
	findHyperlinkAtCoOrds : function( showCoOrds )
	{
		var kDebugFindHyperlinkAtCoOrds = "";//"findHyperlinkAtCoOrds";

		var numHyperlinks = 0;
		var iHyperlink;

		if( this.activeHyperlinks != null )
		{
			numHyperlinks = this.activeHyperlinks.length;
		}
		
		debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "showCoOrds: (" + showCoOrds.pointX + "," + showCoOrds.pointY + ")" );

		debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "- looking through " + numHyperlinks + " hyperlinks..." );
		
		for( iHyperlink = numHyperlinks; iHyperlink > 0; iHyperlink-- )
		{
			var hyperlink		= this.activeHyperlinks[iHyperlink-1];
			var hyperlinkRect	= hyperlink.targetRectangle;
			
			hyperlinkLeft		= hyperlinkRect.x;
			hyperlinkTop		= hyperlinkRect.y;
			hyperlinkRight		= hyperlinkLeft + hyperlinkRect.width;
			hyperlinkBottom		= hyperlinkTop	+ hyperlinkRect.height;
			
			debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "- " + iHyperlink + ": (left: " + hyperlinkLeft + ", top:" + hyperlinkTop + ", right: " + hyperlinkRight + ", bottom: " + hyperlinkBottom + ", width: " + hyperlinkRect.width + ", height: " + hyperlinkRect.height + ")" );

			if( ( showCoOrds.pointX >= hyperlinkLeft )
			&&  ( showCoOrds.pointX <= hyperlinkRight )
			&&  ( showCoOrds.pointY >= hyperlinkTop )
			&&  ( showCoOrds.pointY <= hyperlinkBottom ) )
			{
				debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "---- found it: url: " + hyperlink.url );
				return hyperlink.url;
			}
			else
			{
				debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "---- not in this one..." );
			}
		}
		
		debugMessage( kDebugShowController, kDebugFindHyperlinkAtCoOrds, "- no hyperlink containing this point found." );
		
		return "";
	},
	
	handleSwipeEvent : function( event )
	{
		var kDebugHandleSwipeEvent = "";//"handleSwipeEvent";

		debugMessage( kDebugShowController, kDebugHandleSwipeEvent, "direction: " + event.memo.direction + " fingers: " + event.memo.fingers );
		
		if( event.memo.direction == "left" )
		{
			switch( event.memo.fingers )
			{
				case 1:
					this.advanceToNextBuild();
					break;
				case 2:
					this.advanceToNextSlide();
					break;
				default:
					break;
			}
		}
		else if( event.memo.direction == "right" )
		{
			switch( event.memo.fingers )
			{
				case 1:
					this.goBackToPreviousBuild();
					break;
				case 2:
					this.goBackToPreviousSlide();
					break;
				default:
					break;
			}
		}
		else
		{
			debugMessage( kDebugShowController, kDebugHandleSwipeEvent, "unsupported swipe" );
		}
	},
	
	toggleMute : function()
	{
		var kDebugToggleMute = "toggleMute";
		
		if( this.muted )
		{
			debugMessage( kDebugShowController, kDebugToggleMute, "turn sound on..." );
			this.muted = false;
		}
		else
		{
			debugMessage( kDebugShowController, kDebugToggleMute, "turn sound off..." );
			this.muted = true;
		}
		
		if( this.soundTrackPlayer )
		{
			debugMessage( kDebugShowController, kDebugToggleMute, "- setting mute state of running soundtrack..." );
			this.soundTrackPlayer.muted = this.muted;
		}
		else
		{
			debugMessage( kDebugShowController, kDebugToggleMute, "- there is no running soundtrack to mute/un-mute" );
		}
		
		debugMessage( kDebugShowController, kDebugToggleMute, "- check if there are any running videos..." );
		
		this.stageManager.setMutedStateOnAllVideoElements( this.muted );
		
		this.updateNavigationButtons(); // this will update the mute button

		debugMessage( kDebugShowController, kDebugToggleMute, "- and we're done!" );
	},
	
	advanceToNextSlide : function()
	{
		var kDebugAdvanceToNextSlide = "advanceToNextSlide";

		if( this.script.showMode == kShowModeHyperlinksOnly )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "can't do it, we're in hyperlinks only mode" );
			return;
		}
		
		if( this.displayManager.infoPanelIsShowing )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "can't do it, info panel is showing" );
			return;
		}

		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "================================================================================" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "===                A D V A N C E   T O   N E X T   S L I D E                 ===" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "================================================================================" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "-             state: " + this.state );
		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "- currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "-    nextSceneIndex: " + this.nextSceneIndex );
	
		switch( this.state )
		{
			case kShowControllerState_IdleAtInitialState:
			case kShowControllerState_IdleAtFinalState:
				var currentSlideIndex		= this.scriptManager.slideIndexFromSceneIndex( this.currentSceneIndex );
				var nextSlideIndex			= currentSlideIndex + 1;
				
				if( nextSlideIndex == this.script.slideCount )
				{
					debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "we're at the end of the show, check to see if it's a looping show..." );
					
					if( this.script.loopSlideshow )
					{
						debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "- yes it is, set next slide to 0" );
						nextSlideIndex = 0;
					}
/*debug*/			else
/*debug*/			{
/*debug*/				debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "- no it is NOT, i guess we're done here" );
/*debug*/			}
				}
				
				if( nextSlideIndex < this.script.slideCount )
				{
					var sceneIndexOfNextSlide	= this.scriptManager.sceneIndexFromSlideIndex( nextSlideIndex );
					
					debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "this.currentSceneIndex: " + this.currentSceneIndex + " currentSlideIndex: " + currentSlideIndex );
					debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "nextSlideIndex: " + nextSlideIndex + " sceneIndexOfNextSlide: " + sceneIndexOfNextSlide );
	
					if( this.textureManager.isScenePreloaded( sceneIndexOfNextSlide ) )
					{
						debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "sceneIndex: " + sceneIndexOfNextSlide + " already preloaded, displaying now..." );
						this.displayScene( sceneIndexOfNextSlide, 0 );
					}
					else
					{
						debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "sceneIndex: " + sceneIndexOfNextSlide + " not yet preloaded, issuing request and waiting..." );
						this.sceneToJumpTo = sceneIndexOfNextSlide;
						this.changeState( kShowControllerState_WaitingToJump );
						this.textureManager.preloadScene( sceneIndexOfNextSlide );
					}
				}
				else
				{
					debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "can't advance, alrady at end of show" );
				}
				break;
				
			default:
				debugMessage( kDebugShowController, kDebugAdvanceToNextSlide, "can't advance now, not in an idle state (currently in '" + this.state + "' state)" );
				break;
		}
	},
	
	advanceToNextBuild : function()
	{
		var kDebugAdvanceToNextBuild = "advanceToNextBuild";

		if( this.script.showMode == kShowModeHyperlinksOnly )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "can't do it, we're in hyperlinks only mode" );
			return;
		}
		
		if( this.displayManager.infoPanelIsShowing )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "can't do it, info panel is showing" );
			return;
		}

		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "================================================================================" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "===                A D V A N C E   T O   N E X T   B U I L D                 ===" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "================================================================================" );
		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "-             state: " + this.state );
		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "- currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "-    nextSceneIndex: " + this.nextSceneIndex );
		
		switch( this.state )
		{
			case kShowControllerState_IdleAtFinalState:
				if( this.nextSceneIndex == -1 )
				{
					debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "- this.nextSceneIndex is -1, that's the end!" );
					break;
				}
				debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "- we're sitting idle on final state of " + this.currentSceneIndex + ", jump to next scene... (" + this.nextSceneIndex + ")" );
				var playAnimations;
				if( this.nextSceneIndex > this.currentSceneIndex )
				{
					debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "next > current, we're NOT looping back to the start, so play animations after jump" );
					playAnimations = true;
				}
				else
				{
					debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "next <= current, we ARE looping back to the start, so do NOT play animations after jump" );
					playAnimations = false;
				}
				this.jumpToScene( this.nextSceneIndex, playAnimations );
				this.textureManager.preloadScene( this.nextSceneIndex );
				break;
				
			case kShowControllerState_IdleAtInitialState:
				debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "- we're sitting idle on initial state of " + this.currentSceneIndex + ", preload next scene (" + this.nextSceneIndex + "), and play current scene..." );
				this.textureManager.preloadScene( this.nextSceneIndex );
				this.playCurrentScene();
				break;
				
			default:
				debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "nextSceneIndex: " + this.nextSceneIndex + " can't advance now, not in an idle state (currently in '" + this.state + "' state)" );
				break;
		}
	},

	goBackToPreviousSlide : function()
	{
		var kDebugGoBackToPreviousSlide = "goBackToPreviousSlide";

		if( this.script.showMode == kShowModeHyperlinksOnly )
		{
			debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "can't do it, we're in hyperlinks only mode" );
			return;
		}
		
		if( this.displayManager.infoPanelIsShowing )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "can't do it, info panel is showing" );
			return;
		}

		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "================================================================================" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "===            G O   B A C K   T O   P R E V I O U S   S L I D E             ===" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "================================================================================" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "-             state: " + this.state );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "-    nextSceneIndex: " + this.nextSceneIndex );
	
		switch( this.state )
		{
			case kShowControllerState_IdleAtFinalState:
			case kShowControllerState_IdleAtInitialState:
				var sceneIndexToUse = this.currentSceneIndex;
				if( this.state == kShowControllerState_IdleAtFinalState )
				{
					sceneIndexToUse++;
				}
				if( (sceneIndexToUse == 0) && (this.script.loopSlideshow == false) )
				{
					debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- can't go back, already at start of show" );
				}
				else
				{
					debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- currentSceneIndex: " + this.currentSceneIndex );

					var currentSlideIndex 			= this.scriptManager.slideIndexFromSceneIndex( sceneIndexToUse );
					var previousSlideIndex			= (currentSlideIndex > 0 ? currentSlideIndex - 1 : this.script.slideCount - 1);
					var sceneIndexOfPreviousSlide	= this.scriptManager.sceneIndexFromSlideIndex( previousSlideIndex );
					
					debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- currentSlideIndex: " + currentSlideIndex );
					debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- previousSlideIndex: " + previousSlideIndex );
					debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "- sceneIndexOfPreviousSlide: " + sceneIndexOfPreviousSlide + " jumping to it now..." );
	
					this.jumpToScene( sceneIndexOfPreviousSlide );
				}
				break;
				
			default:
				debugMessage( kDebugShowController, kDebugGoBackToPreviousSlide, "can't go back now, not in an idle state (currently in '" + this.state + "' state)" );
				break;
		}
	},
	
	goBackToPreviousBuild : function()
	{
		var kDebugGoBackToPreviousBuild = "goBackToPreviousBuild";

		if( this.script.showMode == kShowModeHyperlinksOnly )
		{
			debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "can't do it, we're in hyperlinks only mode" );
			return;
		}
		
		if( this.displayManager.infoPanelIsShowing )
		{
			debugMessage( kDebugShowController, kDebugAdvanceToNextBuild, "can't do it, info panel is showing" );
			return;
		}

		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "================================================================================" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "===            G O   B A C K   T O   P R E V I O U S   B U I L D             ===" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "================================================================================" );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "-             state: " + this.state );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "- currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "-    nextSceneIndex: " + this.nextSceneIndex );

		if( (this.currentSceneIndex == 0) && (this.script.loopSlideshow == false) )
		{
			debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "can't go back, already at start of show" );
		}
		else
		{
			var previousSceneIndex;
			
			if( this.currentSceneIndex == 0 )
			{
				debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "was at first scene of show, going to last scene..." ); // or should it be last slide? 
				previousSceneIndex = this.script.numScenes - 1;
			}
			//else if( this.currentSceneIndex == this.script.page Count )
			//{
			//	debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "was at black screen at end of show, going to last scene..." );
			//	previousSceneIndex = this.script.page Count - 1;
			//}
			else
			{
				switch( this.state )
				{
					case kShowControllerState_IdleAtFinalState:
						previousSceneIndex = this.currentSceneIndex;
						debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "we're somewhere in the middle of the desk sitting on the final state of a scene, this.currentSceneIndex: " + this.currentSceneIndex + " previousSceneIndex: " + previousSceneIndex );
						break;

					case kShowControllerState_IdleAtInitialState:
						previousSceneIndex = this.currentSceneIndex - 1;
						debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "we're somewhere in the middle of the desk sitting on the initial state of a scene, this.currentSceneIndex: " + this.currentSceneIndex + " previousSceneIndex: " + previousSceneIndex );
						break;

					default:
						debugMessage( kDebugShowController, kDebugGoBackToPreviousBuild, "we're somewhere in the middle of the desk in stete: " + this.state + " - what do we do now?" );
						break;
				}
			}
			this.jumpToScene( previousSceneIndex, false );
		}
	},
	
	handleStageIsReadyEvent : function( event )
	{
		var kDebugHandleStageIsReadyEvent = "";//"handleStageIsReadyEvent";

		debugMessage( kDebugShowController, kDebugHandleStageIsReadyEvent );
	},
	
	handleScriptDidDownloadEvent : function( event )
	{
		var kDebugHandleScriptDidDownloadEvent = "handleScriptDidDownloadEvent";

		debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent );
		
		switch( this.state )
		{
			case kShowControllerState_DownloadingScript:
				this.script = event.memo.script;
				this.script.numScenes = this.script.eventTimelines.length - 1;

				debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent, "- this.script.numScenes: " + this.script.numScenes );

				debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent, "- switching to 'starting'..." );

				this.changeState( kShowControllerState_Starting );
		
				debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent, "- checking to see if a starting slide number was specified in url..." );
				
				var startingSlide = getUrlParameter( "currentSlide" );
				var startingSlideNumber;
				
				if( this.script.showMode == kShowModeHyperlinksOnly )
				{
					this.displayManager.setHyperlinksOnlyMode();
				}
				
				if( startingSlide )
				{
					startingSlideNumber	= parseInt( startingSlide );
				}
				else
				{
					debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent, "- nope, not there, use 1..." );
					startingSlideNumber = 1;
				}
				this.jumpToSlide( startingSlideNumber );	
				break;
			
			default:
				debugMessage( kDebugShowController, kDebugHandleScriptDidDownloadEvent, "- hmmm we seem to have arrived here from an unpredicted state" );
				break;
		}
	},
	
	handleScriptDidNotDownloadEvent : function( event )
	{
		var kDebugHandleScriptDidNotDownloadEvent = "handleScriptDidNotDownloadEvent";

		debugMessage( kDebugShowController, kDebugHandleScriptDidNotDownloadEvent );

		var tryAgain = this.promptUserToTryAgain( kUnableToReachiWorkTryAgain );
		
		if( tryAgain )
		{
			this.scriptManager.downloadScript();
		}
		else
		{
			// TODO: handle error condition here.  Perhaps display some message like "cannot play show at current time"?
			this.displayManager.hideWaitingIndicator();
		}
	},
	
	startSoundTrack : function()
	{
		var kDebugStartSoundTrack = "startSoundTrack";
		
		if( gMode == kModeMobile )
		{
			debugMessage( kDebugShowController, kDebugStartSoundTrack, "soundTrackMode are currently not supported on the phone..." );
			return;
		}
		
		if( this.script.soundTrackMode == kSoundTrackModeOff )
		{
			debugMessage( kDebugShowController, kDebugStartSoundTrack, "soundTrackMode is 'off', nothing to do here..." );
			return;
		}
		
		if( this.script.soundTrackMedia == null )
		{
			debugMessage( kDebugShowController, kDebugStartSoundTrack, "soundTrackMedia is null, nothing to do here..." );
			return;
		}

		debugMessage( kDebugShowController, kDebugStartSoundTrack, "soundTrackMedia is present, start it playing... (There are " + this.script.soundTrackMedia.length + " items)" );
		
		this.currentSoundTrackIndex = 0;
		this.playNextItemInSoundTrack();
		this.updateNavigationButtons(); // this will enable the mute button
	},
	
	playNextItemInSoundTrack : function()
	{
		var kDebugPlayNextItemInSoundTrack = "playNextItemInSoundTrack";
		
		//debugMessage( kDebugShowController, kDebugPlayNextItemInSoundTrack, "look up ID and URL of next itme to play..." );

		var itemId	= this.script.soundTrackMedia[this.currentSoundTrackIndex];
		var itemUrl	= this.textureManager.urlForMovie( itemId );
		
		//debugMessage( kDebugShowController, kDebugPlayNextItemInSoundTrack, "- itemId: " + itemId );
		//debugMessage( kDebugShowController, kDebugPlayNextItemInSoundTrack, "- itemUrl: " + itemUrl );
		
		debugMessage( kDebugShowController, kDebugPlayNextItemInSoundTrack, "time: " + Date().toString() + ", start item " + this.currentSoundTrackIndex + " playing..." );
		
		this.soundTrackPlayer = new Audio();
		
		var self = this;
		
		this.soundTrackPlayer.muted = this.muted;
		this.soundTrackPlayer.style.display = "none";
		this.soundTrackPlayer.src = itemUrl;
		this.soundTrackPlayer.observe( "ended", function( event ) { self.soundTrackItemDidComplete(); }, false );
		this.soundTrackPlayer.load();
		this.soundTrackPlayer.play();
	},
	
	soundTrackItemDidComplete : function()
	{
		var kDebugSoundTrackItemDidComplete = "soundTrackItemDidComplete";

		debugMessage( kDebugShowController, kDebugSoundTrackItemDidComplete, "time: " + Date().toString() + ", item " + this.currentSoundTrackIndex + " completed playing, lets see if there's anything else to play..." );
		
		this.currentSoundTrackIndex++;
	
		if( this.currentSoundTrackIndex < this.script.soundTrackMedia.length )
		{
			debugMessage( kDebugShowController, kDebugSoundTrackItemDidComplete, "- why yes there is..." );
		
			this.playNextItemInSoundTrack();
		}
		else
		{
			if( this.script.soundTrackMode == kSoundTrackModePlayOnce )
			{
				debugMessage( kDebugShowController, kDebugSoundTrackItemDidComplete, "- nope, that's it" );
				this.soundTrackPlayer = null;
				this.updateNavigationButtons(); // this will disable the mute button
			}
			else if( this.script.soundTrackMode == kSoundTrackModeLooping )
			{
				debugMessage( kDebugShowController, kDebugSoundTrackItemDidComplete, "- nope, but we're in loop mode so take it from the top..." );
				this.startSoundTrack();
			}
		}
	},
	
	promptUserToTryAgain : function( message )
	{
		var kDebugPromptUserToTryAgain = "promptUserToTryAgain";

		var tryAgain = false;
		
		if( gMode == kModeMobile )
		{
			debugMessage( kDebugShowController, kDebugPromptUserToTryAgain, "we're in mobile mode, pop up a 'confirm' dialog..." );
			tryAgain = confirm( kUnableToReachiWorkTryAgain );
		}
		else
		{
			debugMessage( kDebugShowController, kDebugPromptUserToTryAgain, "we're in desktop mode, pop up a 'confirm' dialog..." );
			// TODO: interface with heckler to find out if user wants to try again
			//tryAgain = ????
			tryAgain = confirm( kUnableToReachiWorkTryAgain );
		}
		
		return tryAgain;
	},
	
	jumpToSlide : function( slideNumber )
	{
		var slideIndex = slideNumber - 1;
		
		var sceneIndex = this.scriptManager.sceneIndexFromSlideIndex( slideIndex );
		
		this.jumpToScene( sceneIndex, false );
	},

	jumpToScene : function( sceneIndex, playAnimations )
	{
		var kDebugJumpToScene = "jumpToScene";
		
/*debug*/	//kDebugJumpToScene = "";
		
		debugMessage( kDebugShowController, kDebugJumpToScene, "sceneIndex: " + sceneIndex + " playAnimations: " + (playAnimations ? "true" : "false") + " state: " + this.state );
		
		// TODO: rethink whether we need to test state in jumpToSlide
		switch( this.state )
		{
			case kShowControllerState_Starting:
			case kShowControllerState_IdleAtInitialState:
			case kShowControllerState_IdleAtFinalState:
			case kShowControllerState_ReadyToJump:
				break;

			default:
				debugMessage( kDebugShowController, kDebugJumpToScene, "- '" + this.state + "' state does not supports jumping..." );
				return;
		}

		if( this.textureManager.isScenePreloaded( sceneIndex ) == false )
		{
			debugMessage( kDebugShowController, kDebugJumpToScene, "- not yet preloaded, issuing request and waiting..." );

			this.jumpParameters = { sceneToJumpTo : sceneIndex, playAnimationsAfterJumping : playAnimations };
			this.changeState( kShowControllerState_WaitingToJump );
			this.textureManager.preloadScene( sceneIndex );
			return;
		}

		this.changeState( kShowControllerState_SettingUpScene );

		debugMessage( kDebugShowController, kDebugJumpToScene, "- state changed (UI controls should disable), run partTwo in next event loop..." );

		var self = this;
		
		runInNextEventLoop( function() { self.jumpToScene_partTwo( sceneIndex, playAnimations ); } );
	},
	
	jumpToScene_partTwo : function( sceneIndex, playAnimations )
	{
		var kDebugJumpToScene = "jumpToScene_partTwo";

		debugMessage( kDebugShowController, kDebugJumpToScene, "sceneIndex: " + sceneIndex + " playAnimations: " + playAnimations );

		this.createAnimationsForScene( sceneIndex );
		this.displayScene( sceneIndex, playAnimations ? 1 : 0 );
		
		if( playAnimations )
		{
			debugMessage( kDebugShowController, kDebugJumpToScene, "- playAnimations was set to 'true' so let 'er rip..." );
			
			this.playCurrentScene();
		}
		else
		{
			this.changeState( kShowControllerState_IdleAtInitialState );
		}
	},

	calculateNextSceneIndex : function( sceneIndex )
	{
		var kDebugCalculateNextSceneIndex = "calculateNextSceneIndex";

		var nextSceneIndex = this.calculateNextSceneIndex_internal( sceneIndex );
		
		if( gMode == kModeMobile )
		{
			if( this.scriptManager.isOnlyActionInSceneAMovieStart( nextSceneIndex ) )
			{
				debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "we're on a phone and the next build is only a movie start, go to one after that..." )

				nextSceneIndex = this.calculateNextSceneIndex_internal( nextSceneIndex );
			}
		}
		
		return nextSceneIndex;
	},

	calculateNextSceneIndex_internal : function( sceneIndex )
	{
		var kDebugCalculateNextSceneIndex = "calculateNextSceneIndex_internal";
		
		var nextSceneIndex = -1;
		
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "using sceneIndex: " + sceneIndex );
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "-          state: " + this.state );
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "-      numSlides: " + this.script.slideCount );
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "-      numScenes: " + this.script.numScenes );
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "- lastSceneIndex: " + this.script.lastSceneIndex );

		if( sceneIndex < this.script.lastSceneIndex ) 
		{
			nextSceneIndex = sceneIndex + 1;
			debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "- we're NOT at the end, so set it to current + 1... (" + sceneIndex + " + 1 = " + nextSceneIndex + ")" );
		}
		else
		{
			if( this.script.loopSlideshow )
			{
				nextSceneIndex = 0;
				debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "- we're at the end, and we're looping so set it to 0..." );
			}
			else
			{
				nextSceneIndex = -1;
				debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "- we're at the end, and we're NOT looping so set it to -1..." );
			}
		}
		debugMessage( kDebugShowController, kDebugCalculateNextSceneIndex, "---------- for sceneIndex: " + sceneIndex +  " nextSceneIndex is: " + nextSceneIndex );
		
		return nextSceneIndex;
	},
	
	assignNextSceneIndex : function()
	{
		var kDebugAssignNextSceneIndex = "assignNextSceneIndex";

		debugMessage( kDebugShowController, kDebugAssignNextSceneIndex );

		this.nextSceneIndex = this.calculateNextSceneIndex( this.currentSceneIndex );

		this.textureManager.preloadScene( this.nextSceneIndex );
	},
	
	playCurrentScene : function()
	{
		var kDebugPlayCurrentScene = "";//"playCurrentScene";
		
		debugMessage( kDebugShowController, kDebugPlayCurrentScene, "this.currentSceneIndex: " + this.currentSceneIndex + " this.overallEndTime: " + this.overallEndTime );
				
		this.changeState( kShowControllerState_Playing );
		
		var anyMovies = this.applyAnimationsForScene();
		
		//if( anyMovies == false )
		{
			var self = this;
			setTimeout( function() { self.currentSceneDidComplete(); }, this.overallEndTime * 1000 + 100 ); // convert seconds to milliseconds and add 100 ms to be safe
		}
	},
	
	currentSceneDidComplete : function()
	{
		var kDebugCurrentSceneDidComplete = "";//"currentSceneDidComplete";
		
		debugMessage( kDebugShowController, kDebugCurrentSceneDidComplete, "this.currentSceneIndex: " + this.currentSceneIndex );
		
		this.changeState( kShowControllerState_IdleAtFinalState );

		if( this.nextSceneIndex == -1 )
		{
			debugMessage( kDebugShowController, kDebugCurrentSceneDidComplete, "- next scene is -1, nothing to do except update navigation button status" );
			this.updateNavigationButtons();
		}
		else if( this.script.eventTimelines[this.nextSceneIndex].automaticPlay == 1 )
		{ 
			debugMessage( kDebugShowController, kDebugCurrentSceneDidComplete, "- next scene (" + this.nextSceneIndex + ") is autoplay, invoking advanceToNextBuild() on next event loop..." );

			var self = this;
	
			runInNextEventLoop( function() { self.advanceToNextBuild(); } );
		}
		else
		{
			debugMessage( kDebugShowController, kDebugCurrentSceneDidComplete, "- we just finished animating, should be waiting on end state, update navigation button status and add hyperlinks of next scene..." );

			this.updateNavigationButtons();
			this.createHyperlinks( this.nextSceneIndex > this.currentSceneIndex ? this.nextSceneIndex : this.currentSceneIndex );
		}
		// TODO: if there's a movie in the next scene we should jump to it then stop without playing its animations
	},
	
	ensureInitialStateHasEmphasisTransform : function( sceneIndex, textureId, keyName, eventInitialStates, fromValue )
	{
		var kDebugEnsureInitialStateHasEmphasisTransform = "";//"ensureInitialStateHasEmphasisTransform";
		
		var initialEmphasisTransformIsPresent = false;
		
		var textureInitialState = this.initialStateForTexture( sceneIndex, textureId );
		
		switch( keyName )
		{
			case "translationEmphasis":
				initialEmphasisTransformIsPresent = (textureInitialState.translationEmphasis != null);
				break;
				
			case "rotationEmphasis":
				initialEmphasisTransformIsPresent = (textureInitialState.rotationEmphasis != null);
				break;

			case "opacityMultiplier":
				initialEmphasisTransformIsPresent = (textureInitialState.opacityMultiplier != null);
				break;

			case "scaleEmphasis":
				initialEmphasisTransformIsPresent = (textureInitialState.scaleEmphasis != null);
				break;

		} // switch
		
		if( initialEmphasisTransformIsPresent )
			return;
			
		debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "textureId: '" + textureId + "' has no initial emphasis transform" );

		switch( keyName )
		{
			case "translationEmphasis":
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- using fromValue.translationEmphasis: (" + fromValue.translationEmphasis + ")" );
				textureInitialState.translationEmphasis = fromValue.translationEmphasis;
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- translationEmphasis is now: " + textureInitialState.translationEmphasis );
				break;
				
			case "rotationEmphasis":
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- using fromValue.rotationEmphasis: (" + fromValue.rotationEmphasis + ")" );
				textureInitialState.rotationEmphasis = fromValue.rotationEmphasis;
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- rotationEmphasis is now: " + textureInitialState.rotationEmphasis );
				break;

			case "opacityMultiplier":
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- using fromValue.opacityMultiplier: (" + fromValue.opacityMultiplier + ")" );
				textureInitialState.opacityMultiplier = fromValue.opacityMultiplier;
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- opacityMultiplier is now: " + textureInitialState.opacityMultiplier );
				break;

			case "scaleEmphasis":
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- using fromValue.scaleEmphasis: (" + fromValue.scaleEmphasis + ")" );
				textureInitialState.scaleEmphasis = fromValue.scaleEmphasis;
				debugMessage( kDebugShowController, kDebugEnsureInitialStateHasEmphasisTransform, "- scaleEmphasis is now: " + textureInitialState.scaleEmphasis );
				break;

		} // switch
							
	},
	
	preProcessSceneAnimations : function( sceneIndex, eventAnimations, eventInitialStates )
	{
		var kDebugPreProcessSceneAnimations = "";//"preProcessSceneAnimations";

		var debugTimer = new DebugTimer( "preProcessSceneAnimations" );
		
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- First, calculate overall duration of eventTimeline -" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------" );
		
		var iEventAnimation		= 0;
		var numEventAnimations	= eventAnimations.length;

		this.overallEndTime	= 0;
		
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- numEventAnimations: " + numEventAnimations );
		
		for( iEventAnimation = 0; iEventAnimation < numEventAnimations; iEventAnimation++ )
		{
			var eventAnimation	= eventAnimations[iEventAnimation];
			var beginTime		= eventAnimation.beginTime;
			var duration		= eventAnimation.duration;
			var endTime			= beginTime + duration;
			
			debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- iEventAnimation: " + iEventAnimation + ": beginTime: " + beginTime + ": duration: " + duration + " endTime: " + endTime );

			if( endTime > this.overallEndTime )
			{
				this.overallEndTime = endTime;
				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-   that's a new maximum, overallEndTime now " + this.overallEndTime );
			}
			
		} // for( iEventAnimation ) // first time

		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "this.overallEndTime: " + this.overallEndTime );

		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- Second, build x-ref table between canvasObjectID and applicable textureIds -" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------------------------------" );

		var canvasObjectToTextureLookupTable = {};
		
		var iEventInitialState;
		var numEventInitialStates = eventInitialStates.length;
		
		for( iEventInitialState = 0; iEventInitialState < numEventInitialStates; iEventInitialState++ )
		{
			var eventInitialState	= eventInitialStates[iEventInitialState];
			var canvasObjectID		= eventInitialState.canvasObjectID;
			
			debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- textureId: " + eventInitialState.texture );
			
			if( canvasObjectID != null )
			{
				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "---- has a canvasObjectID: " + canvasObjectID );
				
				var applicableTextures = canvasObjectToTextureLookupTable[canvasObjectID];
				
				if( applicableTextures == null )
				{
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------ this is the first texture with this canvasObjectID, creating entry in table..." );
					applicableTextures = new Array();
					canvasObjectToTextureLookupTable[canvasObjectID] = applicableTextures;
				}
				else
				{
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------ there are already textures with this canvasObjectID..." );
				}
				
				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------ appending textureId to array for  this canvasObjectID..." );
				applicableTextures.push( eventInitialState.texture );
			}
		}
		
/*debug*/		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- dumping table..." );
/*debug*/		
/*debug*/		for( var canvasObjectID in canvasObjectToTextureLookupTable )
/*debug*/		{
/*debug*/			debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- canvasObjectID: " + canvasObjectID );
/*debug*/			
/*debug*/			var applicableTextures = canvasObjectToTextureLookupTable[eventInitialState.canvasObjectID];
/*debug*/			var iTexture;
/*debug*/			var numTextures = applicableTextures.length;
/*debug*/			
/*debug*/			for( iTexture = 0; iTexture < numTextures; iTexture++ )
/*debug*/			{
/*debug*/				var textureId = applicableTextures[iTexture];
/*debug*/				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "--- textureId: " + textureId );
/*debug*/			}
/*debug*/		}
		
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- Third, calculate individual delay and duration of each property animation  -" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------------------------------------------------------------------------------" );

		this.textureAnimations = {};

		for( iEventAnimation = 0; iEventAnimation < numEventAnimations; iEventAnimation++ )
		{
			debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- iEventAnimation: " + iEventAnimation );
			
			var eventAnimation	= eventAnimations[iEventAnimation];
			var actions			= eventAnimation.actions;
			
			if( actions == null )
			{
				actions = eventAnimation.noPluginActions;
			}
			
			// Check if this is an "action build"...
			
			var isActionBuild	= false;
			var textureArray	= null;
			
			if( ( eventAnimation.animationType != null )
			&&  ( eventAnimation.animationType == "actionBuild" ) )
			{
				isActionBuild = true;

				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "--- this one is an actionBuild..." );
				
				var canvasObjectId = eventAnimation.canvasObjectID;
				textureArray = canvasObjectToTextureLookupTable[canvasObjectId];
				
				if( typeof(textureArray) == "undefined" )
				{
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- textureArray is undefined, using empty array" );
					textureArray = new Array();
				}
			}
			
			var numActions = actions.length;
			var iAction;
			
			for( iAction = 0; iAction < numActions; iAction++ )
			{
				var action = actions[iAction];
				
				// if this is NOT an actionBuild...
				// - create an array with the single textureId this action applies to
				
				if( isActionBuild == false )
				{
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "--- this one is NOT an actionBuild, create an array with 1 texture in it..." );
					var textureId = action.texture;
					textureArray = new Array();
					textureArray[0] = textureId;
				}
				
				// loop over all textures in the array
				
				var iTexture;
				var numTextures = textureArray.length;
				
				for( iTexture = 0; iTexture < numTextures; iTexture++ )
				{
					var textureId		= textureArray[iTexture];
					
					var beginTime		= action.beginTime;
					var duration		= action.duration;
					var endTime			= beginTime + duration;
					var keyName			= action.action;
					
					// TODO: temp hack to work around z-position vs. z-order ambiguity issue in KPF

					if( keyName == "transform.translation.z" ) 
					{
						if( eventAnimation.effect != "apple:revolve" )
						{
							keyName = "z.order"; // TODO: should this be "zIndex" instead of "z.order"?
						}
					}

					// end hack
					
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-   texure: " + textureId );
	
					var textureAnimation = this.textureAnimations[textureId];
					
					if( textureAnimation == null )
					{
						debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-     - was not in textureAnimations yet, adding now..."  );
						textureAnimation = 
						{ 
							numAnimatedKeys : 0,
							numRequiredDivs : 0,
							keyAnimations: {}
						};
						this.textureAnimations[textureId] = textureAnimation;
					}
					
					var keyAnimation = textureAnimation.keyAnimations[keyName];
					
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-     keyName: " + keyName );
	
					if( keyAnimation == null )
					{
						debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-       - was not in textureAnimation.keyAnimations yet, adding now..."  );
						keyAnimation = 
						{
							earliestBeginTime: 99999,
							latestEndTime: -1,
							keyActions: []
						}
						
						textureAnimation.keyAnimations[keyName] = keyAnimation;
						textureAnimation.numAnimatedKeys++;
						
						if( (keyName != "opacity" )
						&&  (keyName != "isPlaying"  )
						&&  (keyName != "zOrderHint"  )
						&&  (keyName != "hidden"  ) )
						{
							textureAnimation.numRequiredDivs++;
						}
					}
					
					if( beginTime < keyAnimation.earliestBeginTime )
					{
						debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-       beginTime of " + beginTime + " is a new earlist, updating..." );
						keyAnimation.earliestBeginTime = beginTime;
					}
					
					if( endTime > keyAnimation.latestEndTime )
					{
						debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-       endTime of " + endTime + " is a new latest, updating..." );
						keyAnimation.latestEndTime = endTime;
					}
					
				} // for iTexture
				
			} // for iAction
			
		} // for( iEventAnimation ) // second time
		
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-----------------------------------------------------------------------------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- Fourth, iterate over all actions in all eventAnimations, converting there begin/duration to % of individual duration -" );
		debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-----------------------------------------------------------------------------------------------------------------------" );

		for( iEventAnimation = 0; iEventAnimation < numEventAnimations; iEventAnimation++ )
		{
			debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- iEventAnimation: " + iEventAnimation );
	
			var eventAnimation	= eventAnimations[iEventAnimation];
			var actions			= eventAnimation.actions;
			
			if( actions == null )
			{
				actions = eventAnimation.noPluginActions;
			}
			
			// Check if this is an "action build"...
			
			var isActionBuild	= false;
			var textureArray	= null;
			
			if( ( eventAnimation.animationType != null )
			&&  ( eventAnimation.animationType == "actionBuild" ) )
			{
				isActionBuild = true;

				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "--- this one is an actionBuild..." );
				
				var canvasObjectId = eventAnimation.canvasObjectID;
				textureArray = canvasObjectToTextureLookupTable[canvasObjectId];
				
				if( typeof(textureArray) == "undefined" )
				{
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "- textureArray is undefined, using empty array" );
					textureArray = new Array();
				}
			}
			
			var numActions		= actions.length;
			var iAction;
			
			for( iAction = 0; iAction < numActions; iAction++ )
			{
				var action			= actions[iAction];
				var beginTime		= action.beginTime;
				var duration		= action.duration;
				var endTime			= beginTime + duration;
				var keyName			= action.action;
				
				debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "--- iAction: " + iAction + ", keyName: " + keyName );
	
				// if this is NOT an actionBuild...
				// - create an array with the single textureId this action applies to
				
				if( isActionBuild == false )
				{
					var textureId = action.texture;
					textureArray = new Array();
					textureArray[0] = textureId;
				}

				// loop over all textures in the array
				
				var iTexture;
				var numTextures = textureArray.length;
				
				for( iTexture = 0; iTexture < numTextures; iTexture++ )
				{
					var textureId		= textureArray[iTexture];
					
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "------ iTexture: " + iTexture + ", textureId: " + textureId );
	
					// TODO: temp hack to work around z-position vs. z-order ambiguity issue in KPF
					
					if( keyName == "transform.translation.z" ) 
					{
						if( eventAnimation.effect != "apple:revolve" )
						{
							keyName = "z.order";
						}
					}
					
					// end hack
	
					// TODO: temp hack to ensure initial state has emphasis info
					// - remove once eric fixes export to do this
					
					if( (keyName == "translationEmphasis")
					||  (keyName == "rotationEmphasis")
					||  (keyName == "opacityMultiplier")
					||  (keyName == "scaleEmphasis") )
					{
						this.ensureInitialStateHasEmphasisTransform( sceneIndex, textureId, keyName, eventInitialStates, action.from );
					}
					
					// end hack
					
					var textureAnimation	= this.textureAnimations[textureId];
					
					var keyAnimation		= textureAnimation.keyAnimations[keyName];
					
					var keyActions			= keyAnimation.keyActions;
					
					var animationDuration	= keyAnimation.latestEndTime - keyAnimation.earliestBeginTime;
					
					var adjustedBeginTime	= (beginTime - keyAnimation.earliestBeginTime)
					var adjustedEndTime		= (endTime - keyAnimation.earliestBeginTime)
	
					var startKeyframe	= 0;
					var endKeyframe		= 100;
	
					if( animationDuration > 0 )
					{
						//startKeyframe	= Math.floor( 100 * adjustedBeginTime / animationDuration );
						//endKeyframe		= Math.ceil ( 100 * (endTime   - keyAnimation.earliestBeginTime) / animationDuration );
						startKeyframe	= Math.floor( 100 * beginTime / this.overallEndTime );
						endKeyframe		= Math.floor ( 100 * endTime / this.overallEndTime );
					}
	
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-   Action: " + iAction  );
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-     textureId: " + textureId );
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-       keyName: " + keyName );
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-    overallEndTime: " + this.overallEndTime );
					//debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-         beginTime: " + beginTime + " adjustedBeginTime: " + adjustedBeginTime + " startKeyframe: "+ startKeyframe );
					//debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-           endTime: " + endTime   + " adjustedEndTime: " + adjustedEndTime + " endKeyframe: " + endKeyframe );
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-         beginTime: " + beginTime + " startKeyframe: "+ startKeyframe );
					debugMessage( kDebugShowController, kDebugPreProcessSceneAnimations, "-           endTime: " + endTime   + " endKeyframe: " + endKeyframe );
					
					var newAction = 
					{ 
						startKeyframe: startKeyframe, 
						endKeyframe: endKeyframe,
						from: action.from,
						to: action.to,
						timingFunction: action.timingFunction
					}
	
					if( action.timingFunction == "custom" )
					{
						newAction.timingControlPoint1x = action.timingControlPoint1x;
						newAction.timingControlPoint1y = action.timingControlPoint1y;
						newAction.timingControlPoint2x = action.timingControlPoint2x;
						newAction.timingControlPoint2y = action.timingControlPoint2y;
					}
					keyActions.push( newAction );
	
				} // for iTexture
				
			} // for iAction
			
		} //  // for( iEventAnimation ) // third time

		debugStopTimer( debugTimer );
	},
	
/*	
	debugDumpPreProcessedEventAnimations : function()
	{
		var kDebugDumpPreProcessedEventAnimations = "";//"debugDumpPreProcessedEventAnimations";
		
		if( kDebugDumpPreProcessedEventAnimations == "" )
			return;
			
		debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-------------------------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "- D U M P   O F   P R E P R O C E S S S E D   A N I M A T I O N S -" );
		debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-------------------------------------------------------------------" );
		
		for( var textureId in this.textureAnimations )
		{
			var textureAnimation = this.textureAnimations[textureId];
			
			debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "texture: " + textureId + " (" + textureAnimation.numAnimatedKeys + " animated key(s), Required Divs: " + textureAnimation.numRequiredDivs + ")" );

			var iKey = 0;

			for( var keyName in textureAnimation.keyAnimations )
			{
				var keyAnimation = textureAnimation.keyAnimations[keyName];
				
				var keyActions = keyAnimation.keyActions;
				
				debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-   key " + iKey + ": " + keyName + " (from t=" + keyAnimation.earliestBeginTime + " to t=" + keyAnimation.latestEndTime + " - " + keyActions.length + " actions)" );

				for( var iAction = 0; iAction < keyActions.length; iAction++ )
				{
					var action = keyActions[iAction];
					
					debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-     action: " + iAction  );
					debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-          curve: " + action.timingFunction );
					
					if( action.timingFunction == "custom" )
					{
						debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-           cp1x: " + action.timingControlPoint1x );
						debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-           cp1y: " + action.timingControlPoint1y );
						debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-           cp2x: " + action.timingControlPoint2x );
						debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-           cp2y: " + action.timingControlPoint2y );
					}
					debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-          start: " + action.startKeyframe + " at " + this.cssPropertyValueForActionValue( keyName, action.from ) );
					debugMessage( kDebugShowController, kDebugDumpPreProcessedEventAnimations, "-            end: " + action.endKeyframe   + " at " + this.cssPropertyValueForActionValue( keyName,  action.to ) );
				}
				iKey++;
			}
		}
	},
*/	
	cssPropertyNameForAction : function( actionName )
	{
		switch( actionName )
		{
			case "hidden":
				return kVisibilityPropertyName;
			
			case "anchorPoint":
				return kTransformOriginPropertyName;
				
			case "opacityMultiplier":
				return kOpacityPropertyName;
				
			case "translationEmphasis":
			case "rotationEmphasis":
			case "scaleEmphasis":
			
			case "position":
			
			case "transform":
			case "transform.scale":
			case "transform.rotation.x": 
			case "transform.rotation.y": 
			case "transform.rotation.z": 
			case "transform.translation.x": 
			case "transform.translation.y": 
			case "transform.translation.z": 
				return kTransformPropertyName;

			case "zOrderHint":
				return kZIndexPropertyName;

			default:
				return actionName;
		}
	},

	cssPropertyValueForActionValue : function( actionName, actionValue )
	{
		switch( actionName )
		{
			case "hidden":
				if( actionValue.scalar == true )
				{
					return "hidden";
				}
				else
				{
					return "visible";
				}
			
			case "anchorPoint":
				return actionValue.pointX + " " + actionValue.pointY;
				
			case "position":
				return "translate(" + actionValue.pointX + "px," + actionValue.pointY + "px)";
				
			case "translationEmphasis":
				return "translateX(" + actionValue.translationEmphasis[0] + "px) translateY(" + actionValue.translationEmphasis[1] + "px) translateZ(" + actionValue.translationEmphasis[2] + ")";

			case "rotationEmphasis":
				return "rotateZ(" + actionValue.rotationEmphasis[6] + "rad)"; 
				
			case "scaleEmphasis":
				return "scale3d(" + ensureScaleFactorNotZero( actionValue.scaleEmphasis[3] ) + "," + ensureScaleFactorNotZero( actionValue.scaleEmphasis[4] ) + "," + ensureScaleFactorNotZero( actionValue.scaleEmphasis[5] ) + ")";

			case "transform.scale":
				return "scale(" + ensureScaleFactorNotZero( actionValue.scalar ) + ")";
				
			case "transform.scale.x":
				return "scaleX(" + ensureScaleFactorNotZero( actionValue.scalar ) + ")";
				
			case "transform.scale.y":
				return "scaleY(" + ensureScaleFactorNotZero( actionValue.scalar ) + ")";
				
			case "transform.rotation.x": 
				return "rotateX(" + actionValue.scalar + "rad)";

			case "transform.rotation.y": 
				return "rotateY(" + actionValue.scalar + "rad)";

			case "transform.rotation.z": 
				return "rotateZ(" + actionValue.scalar + "rad)";

			case "transform.translation.x": 
				return "translateX(" + actionValue.scalar + "px)";
				
			case "transform.translation.y": 
				return "translateY(" + actionValue.scalar + "px)";

			case "transform.translation.z": 
				return "translateZ(" + actionValue.scalar + "px)";

			case "zOrderHint":
			case "isPlaying":
			case "zPosition":
			case "opacity":
			case "opacityMultiplier":
				return actionValue.scalar + "";
				
			case "transform": 
				return "matrix3d(" + actionValue.transform + ")";
				
			default:
				return "some value";
		}
	},

	createTimingFunctionForAction : function( action )
	{
		switch( action.timingFunction )
		{
			case "easeIn":
				return "ease-in";

			case "easeOut":
				return "ease-Out";

			case "easeInOut":
			case "easeInEaseOut":
				return "ease-in-out";

			case "custom":
				return "cubic-bezier(" + action.timingControlPoint1x + "," + action.timingControlPoint1y + "," + action.timingControlPoint2x + "," + action.timingControlPoint2y + ")";

			case "linear":
			default:
				return "linear";
				
		}
	},
	
	initialStateForTexture : function( sceneIndex, textureId )
	{
		var kDebugInitialStateForTexture = "initialStateForTexture";

		debugMessage( kDebugShowController, kDebugInitialStateForTexture, "looking for initial state of texture '" + textureId + "' in scene " + sceneIndex );
		
		var eventTimeline		= this.script.eventTimelines[sceneIndex];
		var eventInitialStates	= eventTimeline.eventInitialStates;
		var numTextures			= eventInitialStates.length;
		var iTexture;
		
		debugMessage( kDebugShowController, kDebugInitialStateForTexture, "- there are " + numTextures + " textures in this scene" );

		for( iTexture = 0; iTexture < numTextures; iTexture++ )
		{
			var textureInitialState	= eventInitialStates[iTexture];
			var thisTextureId		= textureInitialState.texture;
			
			debugMessage( kDebugShowController, kDebugInitialStateForTexture, "- looking at: " + thisTextureId  );

			if( thisTextureId == textureId )
			{
				debugMessage( kDebugShowController, kDebugInitialStateForTexture, "- found it!"  );
				return textureInitialState;
			}
		}
		debugMessage( kDebugShowController, kDebugInitialStateForTexture, "- not found!"  );
		
		return null;
	},
	
	createInitialKeyframeValue : function( sceneIndex, textureId, keyName, fromValue )
	{
		var kDebugCreateInitialKeyframeValue = "createInitialKeyframeValue";

		debugMessage( kDebugShowController, kDebugCreateInitialKeyframeValue, "sceneIndex: " + sceneIndex + " textureId: " + textureId + " keyName: " + keyName );

		var actionValue =
		{
		}
		
		var textureInitialState = this.initialStateForTexture( sceneIndex, textureId );
		
		switch( keyName )
		{
			case "opacity":
				actionValue.scalar = textureInitialState.opacity;
				break;
				
			case "hidden":
				actionValue.scalar = textureInitialState.hidden;
				break;
			
			case "position":
				actionValue.pointX = fromValue.pointX;
				actionValue.pointY = fromValue.pointY;
				break;
				
			case "translationEmphasis":
				actionValue.translationEmphasis = textureInitialState.translationEmphasis;
				debugMessage( kDebugShowController, kDebugCreateInitialKeyframeValue, "- got a translationEmphasis, hope this works! actionValue.translationEmphasis: " + actionValue.translationEmphasis );
				break;
				
			case "rotationEmphasis":
				actionValue.rotationEmphasis = textureInitialState.rotationEmphasis;
				debugMessage( kDebugShowController, kDebugCreateInitialKeyframeValue, "- got a rotationEmphasis, hope this works! actionValue.rotationEmphasis: " + actionValue.rotationEmphasis );
				break;
				
			case "scaleEmphasis":
				actionValue.scaleEmphasis = textureInitialState.scaleEmphasis;
				debugMessage( kDebugShowController, kDebugCreateInitialKeyframeValue, "- got a scaleEmphasis, hope this works! actionValue.scaleEmphasis: " + actionValue.scaleEmphasis );
				break;
				
			default:
				actionValue.scalar = 0;
				actionValue.pointX = 0;
				actionValue.pointY = 0;
				actionValue.transform = [ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 ]; // should never need this
				break;
		}
		
		return actionValue;
	},
	
	createAnimationRuleForKeyframe : function( animation, keyframe, keyName, value, timingFunction )
	{
		var kDebugCreateAnimationRuleForKeyframe = "";//"createAnimationsForScene";

		var animationRule 		= this.cssPropertyNameForAction( keyName ) + ": " + this.cssPropertyValueForActionValue( keyName, value ) + "; "
								+ (keyframe < 100 ? "-webkit-animation-timing-function: " + timingFunction + ";" : "" )

		var transformOriginRule = ""
		
		if( this.transformOriginValue != "" )
		{
			transformOriginRule = kTransformOriginPropertyName + ": " + this.transformOriginValue + ";"
		}
		else if( this.emphasisTransformOrigin != "" )
		{
			transformOriginRule = kTransformOriginPropertyName + ": " + this.emphasisTransformOrigin + ";"
			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "emphasisTransformOrigin: " + this.emphasisTransformOrigin );
		}
		
		var ruleText			= keyframe + "% "
								+ "{" 
								+ transformOriginRule
								+ animationRule
								+ "}";
								
		animation.insertRule( ruleText );
		
/*debug*/		if( this.transformOriginValue == "" )
/*debug*/		{
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-   " + ruleText  );
/*debug*/		}
/*debug*/		else
/*debug*/		{
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-   " + keyframe + "%" );
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-   {" ); 
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-      " + transformOriginRule  );
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-      " + animationRule  );
/*debug*/			debugMessage( kDebugShowController, kDebugCreateAnimationRuleForKeyframe, "-   }" ); 
/*debug*/		}
	},

	createAnimationsForScene : function( sceneIndex )
	{
		var kDebugCreateAnimationsForScene 				= "";//"createAnimationsForScene";
		var kDebugCreateAnimationsForScene_Adjustments	= "";//"createAnimationsForScene";
		
		debugMessage( kDebugShowController, kDebugCreateAnimationsForScene );
		debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "---------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- c r e a t e A n i m a t i o n s F o r S c e n e -" );
		debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "---------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- sceneIndex: " + sceneIndex );
		
		var eventTimeline		= this.script.eventTimelines[sceneIndex];
		var eventInitialStates	= eventTimeline.eventInitialStates;
		var eventAnimations		= eventTimeline.eventAnimations;

		this.preProcessSceneAnimations( sceneIndex, eventAnimations, eventInitialStates );
//		this.debugDumpPreProcessedEventAnimations();

		var debugTimer = new DebugTimer( "createAnimationsForScene" );

		var kDelta = 1;
		
		for( var textureId in this.textureAnimations )
		{
			var textureAnimation = this.textureAnimations[textureId];
			
			debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- texture: " + textureId );

			this.transformOriginValue = "";
			
			for( var keyName in textureAnimation.keyAnimations )
			{
				if( keyName == "playing" )
				{
					debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- keyName: " + keyName + " - we don't animate this one..." );
					continue;
				}
				
				var keyAnimation	= textureAnimation.keyAnimations[keyName];
				var keyActions		= keyAnimation.keyActions;
				
				var animationName	= escapeTextureId( textureId ) + "-" + escapeTextureId( keyName );
				var animation		= this.animationManager.createAnimation( animationName );

				debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- @keyframe " + animationName );
				debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- {"  );
	
				if( keyName == "anchorPoint" )
				{
/*debug*/			//debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "-   this is an anchor point action, need special case" );

					var action = keyActions[0];

					var width	= this.scriptManager.script.slideWidth;
					var height	= this.scriptManager.script.slideHeight;
					
					var originalOrigin	= {};
					var newOrigin		= {};
					var originOffset 	= {};
					var offsetValue 	= {};
					
					originalOrigin.x 	= width/2;
					originalOrigin.y 	= height/2;
					
					newOrigin.x			= action.to.pointX * width;
					newOrigin.y			= action.to.pointY * height;

					originOffset.deltaX	= newOrigin.x - originalOrigin.x;
					originOffset.deltaY	= newOrigin.y - originalOrigin.y;
					
/*debug*/			//debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "-   originalOrigin: (" + originalOrigin.x + ", " + originalOrigin.y + ")" );
/*debug*/			//debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "-        newOrigin: (" + newOrigin.x + ", " + newOrigin.y + ")" );
/*debug*/			//debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "-     originOffset: (" + originOffset.deltaX + ", " + originOffset.deltaY + ")" );

					offsetValue.pointX	= -originOffset.deltaX;
					offsetValue.pointY	= -originOffset.deltaY;

					this.createAnimationRuleForKeyframe( animation, 0,   "position", offsetValue, "linear" );
					this.createAnimationRuleForKeyframe( animation, 100, "position", offsetValue );

					// now set the transformOriginValue to force all subsequent animations to have an -webkit-transform-origin value

					this.transformOriginValue = newOrigin.x + "px " + newOrigin.y + "px"; 
				}
				else
				{
					// If first action does not start at keyframe 0%
					// - insert 2 keyframes: 0 and N-1 (where N is the first keyframe in the array)
	
					var action = keyActions[0];
					
					if( action.startKeyframe != 0 )
					{
						var initialValue = this.createInitialKeyframeValue( sceneIndex, textureId, keyName, action.from );

						if( keyName != "hidden" )
						{
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> insert keyframe at 0 because 1st keyframe in list (" + action.startKeyframe + ") is not 0" );
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> and this is NOT a visibility animation"  );

							this.createAnimationRuleForKeyframe( animation, 0, keyName, initialValue, "linear" );
//							this.createAnimationRuleForKeyframe( animation, action.startKeyframe - kDelta, keyName, initialValue, "linear" );

							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> end of inserted keyframe(s)" );
						}
						else
						{
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> insert keyframes at 0 and " + (action.startKeyframe - 1) + " because 1st keyframe in list (" + action.startKeyframe + ") is not 0" );
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> and this IS a visibility animation"  );
	
							this.createAnimationRuleForKeyframe( animation, 0, keyName, initialValue, "linear" );
							this.createAnimationRuleForKeyframe( animation, action.startKeyframe - kDelta, keyName, initialValue, "linear" );

							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> end of inserted keyframe(s)" );
						}
					}
					
					var lastKeyframe	= -1;
					var lastToValue		= null;
					
					//debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- iterating over keyActions... there are " + keyActions.length );
					
					for( var iAction = 0; iAction < keyActions.length; iAction++ )
					{
						action = keyActions[iAction];
					
						if( keyName == "rotationEmphasis" )
						{
							this.emphasisTransformOrigin = action.from.rotationEmphasis[0] + "px " + action.from.rotationEmphasis[1] + "px";
						}
						else
						{
							this.emphasisTransformOrigin = "";
						}
						
						// If this action does not start where the last action ended
						// - insert 1 keyframe: N-1 (where N is the start keyframe of the current action)
						
						if( ( lastKeyframe != -1 )
						&&  ( lastKeyframe != action.startKeyframe ) )
						{
							var insertedKeyFrame = action.startKeyframe - kDelta;
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> insert keyframe at " + insertedKeyFrame + " because previous 'end' keyframe (" + lastKeyframe + ") is not current 'start' keyframe (" + action.startKeyframe + ")"  );
							
							this.createAnimationRuleForKeyframe( animation, insertedKeyFrame, keyName, lastToValue, "linear" );
							
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> end of inserted keyframe(s)" );
						}
						
						var kDebugKeyFrameWasAdjusted = false;
						
						if( lastKeyframe == action.startKeyframe )
						{
							debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> this next keyframe was the same as last one, adjusting by adding 1... (" + action.startKeyframe + " -> " + (action.startKeyframe+1) + ")" );
/*debug*/					kDebugKeyFrameWasAdjusted = true;

							action.startKeyframe += kDelta;
						}
						
						var timingFunction = this.createTimingFunctionForAction( action );

						{
							this.createAnimationRuleForKeyframe( animation, action.startKeyframe, keyName, action.from, timingFunction );
						}
						
/*debug*/ 				if( kDebugKeyFrameWasAdjusted )
/*debug*/				{
/*debug*/					debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> end of adjusted keyframe(s)" );
/*debug*/				}

						this.createAnimationRuleForKeyframe( animation, action.endKeyframe,   keyName, action.to,   "linear" );
	
						lastToValue		= action.to;
						lastKeyframe	= action.endKeyframe;
					
					} // for( iAction )
					
					// If last action does not end at keyframe 100%
					// - insert 2 keyframes: N+1 and 100 (where N is the last keyframe in the array)
					
					if( lastKeyframe != 100 )
					{
						debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> inserting keyframes at " + (lastKeyframe + 1) + " and 100 because last keyframe in list (" + lastKeyframe + ") is not 100"  );
	
						this.createAnimationRuleForKeyframe( animation, lastKeyframe + kDelta, keyName, lastToValue, "linear" );
						this.createAnimationRuleForKeyframe( animation, 100, keyName, lastToValue, "linear" );

						debugMessage( kDebugShowController, kDebugCreateAnimationsForScene_Adjustments, "---> end of inserted keyframe(s)" );
					}
					
				} // else - if( anchorPoint )

				debugMessage( kDebugShowController, kDebugCreateAnimationsForScene, "- }"  );
			}
		}
		debugStopTimer( debugTimer );
	},

	applyAnimationsForScene : function()
	{
		var kDebugApplyAnimationsForScene = "";//"applyAnimationsForScene";
		
		debugMessage( kDebugShowController, kDebugApplyAnimationsForScene );
		debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-------------------------------------------------" );
		debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "- a p p l y A n i m a t i o n s F o r S c e n e -" );
		debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-------------------------------------------------" );

		var anyMovies = false;
		
		for( var textureId in this.textureAnimations )
		{
			var textureAnimation = this.textureAnimations[textureId];
			var escapedTextureId = escapeTextureId( textureId );
			
			debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "- texture: " + textureId + " (escaped: " + escapedTextureId + ")" );

			// TODO: TEMP HACK - some animations are NOT making their textures visible when the animation starts
			// - for now, force all textures to be visible when the animation starts

			var imageElement = document.getElementById( escapedTextureId + "-root" );
			imageElement.style.visibility = "visible";
			
			var clearInitialTransform = false;
			
			// apply animations for each key
			
			for( var keyName in textureAnimation.keyAnimations )
			{
				var keyAnimation		= textureAnimation.keyAnimations[keyName];

				/*
				// TODO: re-evaluate what we do when we get a "playing" action
				if( keyName == "playing" )
				{
					debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "setting timeout to start movie '" + escapedTextureId + " in " + keyAnimation.earliestBeginTime + " seconds..." );
					
					var self = this;
					setTimeout( function() { self.startMoviePlaying( escapedTextureId ); }, keyAnimation.earliestBeginTime * 1000 );
					continue;
				}
				*/

				var keyActions			= keyAnimation.keyActions;
				var lastAction			= keyActions[keyActions.length - 1];

				var animationName		= escapedTextureId + "-" + escapeTextureId( keyName );

				var propertyName		= this.cssPropertyNameForAction( keyName );
				var propertyValue		= this.cssPropertyValueForActionValue( keyName, lastAction.to );

				var animationDuration	= keyAnimation.latestEndTime - keyAnimation.earliestBeginTime;
				
				var elementId	= "";
				var isMovie		= false;
				
				switch( keyName )
				{
					case "opacity":
						elementId = escapedTextureId;
						break;
					
					case "zOrderHint":
					case "transform.translation.z":
					case "hidden":
						elementId = escapedTextureId + "-root";
						break;

					case "isPlaying":
						elementId = escapedTextureId + "-movieObject";
						isMovie = true;
						anyMovies = true;
						break;
					
					case "position":
						clearInitialTransform = true;
						// fall through
						
					case "transform.scale":
					case "transform.scale.x":
					case "transform.scale.y":
						// fall through

					default:
						elementId = animationName;
						break;
				}
				
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-  keyName: " + keyName );

				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-    animationName: " + animationName );
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-        elementId: " + elementId );
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-         duration: " + this.overallEndTime );
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-     propertyName: " + propertyName );
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "-      final value: " + propertyValue );

				var element = document.getElementById( elementId );
				
				if( element )
				{
					if( isMovie )
					{
						if( gMode == kModeMobile )
						{
							debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "- this animation is an 'isPlaying' one, but we're on the phone so don't start the movie..." );
						}
						else
						{
							debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "- this animation is an 'isPlaying' one, start the movie..." );
							
							this.startMoviePlaying( elementId );
							this.updateNavigationButtons(); // we need to update the mute button state
						}
					}
					else
					{
						element.style.setProperty( propertyName, propertyValue );
						element.style.setProperty( kAnimationNamePropertyName, animationName );
						element.style.setProperty( kAnimationDurationPropertyName, this.overallEndTime + "s" );
					}
				}
			}

			// if any of the keys were 2D translation, clear out "initial state" transform
			
			if( clearInitialTransform )
			{
				debugMessage( kDebugShowController, kDebugApplyAnimationsForScene, "- clearing out 'initial state' transform" );

				var textureElement = document.getElementById( escapedTextureId + "-root" );
				
				textureElement.style.setProperty( kTransformPropertyName, "" );
			}
		}
		
		return anyMovies;
	},
	
	startMoviePlaying : function( escapedTextureId )
	{
		var kDebugStartMoviePlaying = "startMoviePlaying";
		
		debugMessage( kDebugShowController, kDebugStartMoviePlaying, "escapedTextureId: " + escapedTextureId );
		
		var movieObject = document.getElementById( escapedTextureId );
		
		if( movieObject )
		{
			if( movieObject.play )
			{
				debugMessage( kDebugShowController, kDebugStartMoviePlaying, "- movieObject.play() is valid, invoking..." );
				
				var self = this;
				
				movieObject.addEventListener( 'play',  function( event ) { self.handleMovieDidStart( event, escapedTextureId ); } );
				movieObject.addEventListener( 'ended', function( event ) { self.handleMovieDidEnd( event, escapedTextureId ); } );
				movieObject.addEventListener( 'error', function( event ) { self.handleMovieError( event, escapedTextureId ); } );
				
				movieObject.play();
				
				debugMessage( kDebugShowController, kDebugStartMoviePlaying, "- movie should be playing now" );
			}
			else
			{
				debugMessage( kDebugShowController, kDebugStartMoviePlaying, "- movieObject.play() is null, that can't be good" );
			}
		}
		else
		{
			debugMessage( kDebugShowController, kDebugStartMoviePlaying, "- could not find movie object by ID!" );
		}
	},
	
	handleMovieDidStart : function( event, escapedTextureId )
	{
		var kDebugHandleMovieDidStart = "handleMovieDidStart";
		
		debugMessage( kDebugShowController, kDebugHandleMovieDidStart, "textureId: " + escapedTextureId   );
	},
	
	handleMovieDidEnd : function( event, escapedTextureId )
	{
		var kDebugHandleMovieDidEnd = "handleMovieDidEnd";
		
		debugMessage( kDebugShowController, kDebugHandleMovieDidEnd, "textureId: " + escapedTextureId   );
	},
	
	handleMovieError : function( event, escapedTextureId )
	{
		var kDebugHandleMovieError = "handleMovieError";
		
		debugMessage( kDebugShowController, kDebugHandleMovieError, "textureId: " + escapedTextureId   );
	},
	
	playScene : function( sceneIndex )
	{
		var kDebugPlayScene = "";//"playScene";
		
		debugMessage( kDebugShowController, kDebugPlayScene, "sceneIndex: " + sceneIndex   );

		switch( this.state )
		{
			case kShowControllerState_IdleAtInitialState:
			case kShowControllerState_IdleAtFinalState:
			case kShowControllerState_ReadyToDisplay:
				break;

			default:
				debugMessage( kDebugShowController, kDebugPlayScene, "- '" + this.state + "' state does not supports playing..." );
				return;
		}

		if( this.textureManager.isScenePreloaded( sceneIndex ) == false )
		{
			debugMessage( kDebugShowController, kDebugPlayScene, "- not yet preloaded, issuing request and waiting..." );
			this.sceneToPlay = sceneIndex;
			this.changeState( kShowControllerState_WaitingToDisplay );
			this.textureManager.preloadScene( sceneIndex );
			return;
		}

		this.changeState( kShowControllerState_SettingUpScene );

		debugMessage( kDebugShowController, kDebugJumpToScene, "- state changed (UI controls should disable), run partTwo in next event loop..." );

		var self = this;
		
		runInNextEventLoop( function() { self.jumpToScene_partTwo( sceneIndex, true ); } );
	},

	createHyperlinks : function( hyperlinkSceneIndex )
	{
		var kDebugCreateHyperlinks = "";//"createHyperlinks";
		
		if( hyperlinkSceneIndex == -1 )
		{
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "hyperlinkSceneIndex is -1, nothing to do" );
			return;
		}
		
		debugMessage( kDebugShowController, kDebugCreateHyperlinks );
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "-               state: " + this.state );
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "-   currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "-      nextSceneIndex: " + this.nextSceneIndex );
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- hyperlinkSceneIndex: " + hyperlinkSceneIndex );
		
/*debug*/ //kDebugCreateHyperlinks = "";

		var eventTimeLine = this.script.eventTimelines[hyperlinkSceneIndex];
		
		if( eventTimeLine == null )
		{
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "eventTimeLine for the specified scene index is null!" );
			return;
			
		}
		var hyperlinks = eventTimeLine.hyperlinks;

		if( hyperlinks == null )
		{
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "hyperlinks property for the specified scene index is null!" );
			return;
		}
		
		var numHyperlinks = hyperlinks.length;
		var iHyperlink;
		
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- iterating over " + numHyperlinks + " hyperlinks..." );

		var kMinHyperlinkWidth	= 150;
		var kMinHyperlinkHeight	= 50;
		
		var showWidth			= this.displayManager.showWidth;
		var showHeight			= this.displayManager.showHeight;
		
		for( iHyperlink = 0; iHyperlink < numHyperlinks; iHyperlink++ )
		{
			var hyperlink		= hyperlinks[iHyperlink];
			var hyperlinkRect	= hyperlink.targetRectangle;

			var activeHyperlink = { targetRectangle : hyperlinkRect, url: hyperlink.url };
				
			var spaceOnLeft		= hyperlinkRect.x;
			var spaceOnTop		= hyperlinkRect.y;
			var spaceOnRight	= showWidth  - (hyperlinkRect.x + hyperlinkRect.width);
			var spaceOnBottom	= showHeight - (hyperlinkRect.y	+ hyperlinkRect.top);
			
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- " + iHyperlink + ": (x:" + hyperlinkRect.x + ", y:" + hyperlinkRect.y + ", w:" + hyperlinkRect.width + ", h:" + hyperlinkRect.height + ")" );
			
			if( gMode == kModeMobile )
			{
				if( hyperlinkRect.width < kMinHyperlinkWidth )
				{
					var deltaWidth	= kMinHyperlinkWidth - hyperlinkRect.width;
					var leftShift	= deltaWidth / 2;
					var rightShift	= deltaWidth / 2;
					
					debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- width too small by: " + deltaWidth );
					
					if( spaceOnLeft < leftShift )
					{
						leftShift = spaceOnLeft;
					}
					else if( spaceOnRight < rightShift )
					{
						leftShift = leftShift + (rightShift - spaceOnRight);
					}
					
					activeHyperlink.targetRectangle.x		-= leftShift;
					activeHyperlink.targetRectangle.width	+= deltaWidth;
				}
				
				if( hyperlinkRect.height < kMinHyperlinkHeight )
				{
					var deltaHeight	= kMinHyperlinkHeight - hyperlinkRect.height;
					var topShift	= deltaHeight / 2;
					var bottomShift	= deltaHeight / 2;
					
					debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- height too small by: " + deltaHeight );
					
					if( spaceOnTop < topShift )
					{
						topShift = spaceOnTop;
					}
					else if( spaceOnBottom < bottomShift )
					{
						topShift = topShift + (rightShift - spaceOnRight);
					}
					
					activeHyperlink.targetRectangle.y		-= topShift;
					activeHyperlink.targetRectangle.height	+= deltaHeight;
				}
			}
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- adding hyperlink to stage..." );

			this.stageManager.addHyperlink( activeHyperlink.targetRectangle );
			
			debugMessage( kDebugShowController, kDebugCreateHyperlinks, "- adding hyperlink struct to array..." );

			this.activeHyperlinks[iHyperlink] = activeHyperlink;
		}
		
		debugMessage( kDebugShowController, kDebugCreateHyperlinks, "finished." );
	},
	
	updateNavigationButtons : function()
	{
		var kDebugUpdateNavigationButtons = "updateNavigationButtons";

		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-             state: " + this.state );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-     loopSlideshow: " + this.script.loopSlideshow );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "- currentSlideIndex: " + this.currentSlideIndex );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "- currentSceneIndex: " + this.currentSceneIndex );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-    nextSceneIndex: " + this.nextSceneIndex );

		var enableBackwardButton = ( this.script.loopSlideshow || (this.currentSlideIndex > 0) );
		var enableForwardButton  = ( this.script.loopSlideshow || (this.state == kShowControllerState_IdleAtInitialState) || (this.nextSceneIndex != -1) );
		
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "- backward button should be: " + (enableBackwardButton ? "enabled" : "disabled") );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-  forward button should be: " + (enableForwardButton  ? "enabled" : "disabled") );

		var anyVideo = this.stageManager.anyVideoElementsOnStage();
		var anyAudio = (this.soundTrackPlayer != null);

		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-                 any Video: " + (anyVideo ? "yes" : "no") );
		debugMessage( kDebugShowController, kDebugUpdateNavigationButtons, "-                 any Audio: " + (anyAudio ? "yes" : "no") );
		
		var muteButtonState = ((anyVideo || anyAudio) ? kMuteButtonState_Enabled : kMuteButtonState_Disabled) | (this.muted ? kMuteButtonState_Muted : kMuteButtonState_NotMuted);
		
		this.displayManager.setPreviousButtonEnabled( enableBackwardButton );
		this.displayManager.setNextButtonEnabled( enableForwardButton );

		this.displayManager.setHudMuteButtonState( muteButtonState ); 
	},
	
	setCurrentSceneIndexTo : function( sceneIndex )
	{
		var kDebugSetCurrentSceneIndexTo = "setCurrentSceneIndexTo";
		
		debugMessage( kDebugShowController, kDebugSetCurrentSceneIndexTo, "changing it from " + this.currentSceneIndex + " to " + sceneIndex );
		
		this.currentSceneIndex = sceneIndex;
		this.assignNextSceneIndex();

		this.updateSlideNumber();
		this.updateNavigationButtons();
	},
	
	displayScene : function( sceneIndex, hyperlinkSceneOffset )
	{
		var kDebugDisplayScene = "displayScene";
		
		debugMessage( kDebugShowController, kDebugDisplayScene, "sceneIndex: " + sceneIndex   );

		if( sceneIndex == -1 )
		{
			debugMessage( kDebugShowController, kDebugDisplayScene, "- sceneIndex is -1, ignoring..." );
			return;
		}

		if( this.firstSlide )
		{
			var self = this;
			
			runInNextEventLoop( function() { self.startSoundTrack(); self.displayManager.clearLaunchMode(); } );
		}

		this.setCurrentSceneIndexTo( sceneIndex );
		
		this.clearAllHyperlinks();

		var eventTimeline		= this.script.eventTimelines[sceneIndex];
		var eventInitialStates	= eventTimeline.eventInitialStates;
		var numTextures			= eventInitialStates.length;
		var zOrder				= 0;
		var iTexture;

/*debug*/ 	kDebugDisplayScene = "";

		debugMessage( kDebugShowController, kDebugDisplayScene, "eventTimeline: " + eventTimeline );
		
		for( var property in eventTimeline )
		{
			debugMessage( kDebugShowController, kDebugDisplayScene, "- " + property + ": " + eventTimeline[property] );
		}

		debugMessage( kDebugShowController, kDebugDisplayScene, "precessing hyperlinks..." ); 
		debugMessage( kDebugShowController, kDebugDisplayScene, "- hyperlinkSceneOffset: " + hyperlinkSceneOffset ); 
		
		debugMessage( kDebugShowController, kDebugDisplayScene, "determing which existig textures to remove from stage... (remove everything but leave movies in place)" );
		
		var stage				= this.stageManager.stage;
		var childNodesToRemove	= new Array();
		var moviesLeftInScene	= {};
		
		for( iTexture = 0; iTexture < stage.childNodes.length; iTexture++ )
		{
			var thisTexture	= stage.childNodes[iTexture];
			var textureId	= thisTexture.id;
			var removeChild	= true;
			
			debugMessage( kDebugShowController, kDebugDisplayScene, "- " + iTexture + ": " + textureId );			
			
			if( thisTexture && typeof thisTexture != "undefined" && typeof textureId != "undefined" && textureId != "" )
			{
				if( this.isMovie( textureId ) )
				{
					debugMessage( kDebugShowController, kDebugDisplayScene, "-    this one's a movie, let's see if it's in the next scene..." );
					if( this.scriptManager.isMovieInScene( this.calculateNextSceneIndex( sceneIndex ), textureId ) )
					{
						debugMessage( kDebugShowController, kDebugDisplayScene, "-    it is! leave it in." );
						removeChild = false;
					}
				}
			}

			if( removeChild )
			{
				debugMessage( kDebugShowController, kDebugDisplayScene, "- add it to list of textures to remove" );
				childNodesToRemove.push( thisTexture );
			}
			else
			{
				debugMessage( kDebugShowController, kDebugDisplayScene, "- add it to list of movies left in scene" );
				moviesLeftInScene[textureId] = true;
			}
		}

		debugMessage( kDebugShowController, kDebugDisplayScene, "- iterating over list of movies we're keeping..." );

		for( movieTextureId in moviesLeftInScene )
		{
			debugMessage( kDebugShowController, kDebugDisplayScene, "- " + movieTextureId );
		}
		
		debugMessage( kDebugShowController, kDebugDisplayScene, "- iterating over list of textures to remove..." );

		for( iTexture = 0; iTexture < childNodesToRemove.length; iTexture++ )
		{
			var thisTexture	= childNodesToRemove[iTexture];
			var textureId	= thisTexture.id;
			debugMessage( kDebugShowController, kDebugDisplayScene, "- " + iTexture + ": " + textureId );			
			stage.removeChild( thisTexture );
		}
		
/*debug*/ kDebugDisplayScene = "";

		debugMessage( kDebugShowController, kDebugDisplayScene, "processing " + numTextures + " textures..." );

		for( iTexture = 0; iTexture < numTextures; iTexture++ )
		{
			var textureInitialState	= eventInitialStates[iTexture];
			var textureId			= textureInitialState.texture;
			var textureAnimation	= this.textureAnimations[textureId];
			var escapedTextureId	= escapeTextureId( textureId ) + "-root";
			
			debugMessage( kDebugShowController, kDebugDisplayScene, "- " + iTexture + ": " + textureId + " (" + escapedTextureId + ")" );

			if( moviesLeftInScene[escapedTextureId] )
			{
				debugMessage( kDebugShowController, "displayScene", "- this movie was in the last scene, no need to add it again..." );
				continue;
			}
			
			var numRequiredDivs		= 0;
			
			if( textureAnimation != null )
			{
				numRequiredDivs = textureAnimation.numRequiredDivs
			}
			var visibility			= (textureInitialState.hidden == 0 ? "visible" : "hidden");
			var opacity				= textureInitialState.opacity;
			var textureTransform	= "";
			var textureProperties	= this.script.textures[textureId];
			var nativeWidth			= textureProperties.width;
			var nativeHeight		= textureProperties.height;
			var use3DTransforms		= true;
			
			zOrder = textureInitialState.zIndex;
			
			if( (use3DTransforms == true)
			&&  (typeof textureInitialState.transform != "undefined") )
			{
				textureTransform = "matrix3D(" + textureInitialState.transform + ")";
			}
			else
			{
				textureTransform = "matrix(" + textureInitialState.affineTransform + ")";
			}
	
			debugMessage( kDebugShowController, kDebugDisplayScene, "         opacity: " + opacity );
			debugMessage( kDebugShowController, kDebugDisplayScene, "      visibility: " + visibility );
			debugMessage( kDebugShowController, kDebugDisplayScene, "     nativeWidth: " + nativeWidth );
			debugMessage( kDebugShowController, kDebugDisplayScene, "    nativeHeight: " + nativeHeight );
			debugMessage( kDebugShowController, kDebugDisplayScene, "       transform: " + textureTransform );
			debugMessage( kDebugShowController, kDebugDisplayScene, "          zOrder: " + zOrder );
			debugMessage( kDebugShowController, kDebugDisplayScene, "        num Divs: " + numRequiredDivs );

			var divNames = new Array();
			
			if( textureAnimation )
			{
				debugMessage( kDebugShowController, kDebugDisplayScene, "adding keyNames to divNames..." );
	
				for( var keyName in textureAnimation.keyAnimations )
				{
					if( (keyName != "opacity" )
					&&  (keyName != "isPlaying"  )
					&&  (keyName != "zOrderHint"  )
					&&  (keyName != "hidden"  ) )
					{
						debugMessage( kDebugShowController, kDebugDisplayScene, "-  " + keyName );
						divNames.push( keyName );
					}
					else
					{
						debugMessage( kDebugShowController, kDebugDisplayScene, "-  " + keyName + " (no div required for this key)" );
					}
				}
			}

			this.stageManager.addTextureToStage( textureId, nativeWidth, nativeHeight, visibility, opacity, zOrder, textureTransform, divNames, textureInitialState );
			//zOrder += 0.1;
			//zOrder += 1;
		}

		var hyperlinkSceneIndex;
		
		switch( hyperlinkSceneOffset )
		{
			case 0:
				debugMessage( kDebugShowController, kDebugDisplayScene, "- use hyperlinks from THIS scene" );
				hyperlinkSceneIndex = this.currentSceneIndex;
				break;
				
			case 1:
				debugMessage( kDebugShowController, kDebugDisplayScene, "- use hyperlinks from NEXT scene" );
				hyperlinkSceneIndex = this.nextSceneIndex;
				break;
				
			default:
				debugMessage( kDebugShowController, kDebugDisplayScene, "- not sure what we should do here" );
				hyperlinkSceneIndex = -1;
				break;
		}
		
		this.createHyperlinks( hyperlinkSceneIndex );

		this.changeState( kShowControllerState_IdleAtInitialState ); 
		this.textureManager.preloadScene( this.nextSceneIndex );
		
		if( this.script.eventTimelines[this.currentSceneIndex].automaticPlay == 1 )
		{
			debugMessage( kDebugShowController, "displayScene", "this scene (" + this.currentSceneIndex + ") is autoplay, invoking advanceToNextBuild() on next event loop..." );

			var self = this;
	
			runInNextEventLoop( function() { self.advanceToNextBuild(); } );
		}

		this.updateNavigationButtons();

		debugMessage( kDebugShowController, kDebugDisplayScene, "complete." );
	},

	isMovie : function( textureId )
	{
		return( textureId.indexOf( "movie" ) != -1 );
	},
	
	handleSceneDidPreloadEvent : function( event )
	{
		var kDebugHandleSceneDidPreloadEvent = "";//"handleSceneDidPreloadEvent";
		
		debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "sceneIndex: " + event.memo.sceneIndex + " state: " + this.state );
		
		this.displayManager.setNextButtonEnabled( this.currentSceneIndex < (this.script.pageCount - 1) );
		
		switch( this.state )
		{
			case kShowControllerState_WaitingToJump:
				if( event.memo.sceneIndex == this.jumpParameters.sceneToJumpTo )
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this is the scene we were waiting to jump to, jumping to it now..." );
					this.changeState( kShowControllerState_ReadyToJump );
					this.jumpToScene( this.jumpParameters.sceneToJumpTo, this.jumpParameters.playAnimationsAfterJumping );
				}
				else
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this (" + event.memo.sceneIndex + ") is NOT the scene we were waiting to jump to (" + this.jumpParameters.sceneToJumpTo + "), hmmm... what to do?" );
				}
				break;
			
			case kShowControllerState_WaitingToDisplay:
				if( event.memo.sceneIndex == this.sceneToDisplay )
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this is the scene we were waiting to display, displaying it now..." );
					this.changeState( kShowControllerState_ReadyToDisplay );
					this.displayScene( this.sceneToDisplay, 0 );
				}
				else
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this is NOT the scene we were waiting to display, hmmm... what to do?" )
				}
				break;
				
			case kShowControllerState_WaitingToPlay:
				if( event.memo.sceneIndex == this.nextSceneIndex )
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this is the next scene, we were waiting for this before we could play the current one, playing now..." )
					this.changeState( kShowControllerState_ReadyToPlay ); 
					this.playCurrentScene();
				}
				else
				{
					debugMessage( kDebugShowController, kDebugHandleSceneDidPreloadEvent, "this is NOT the next scene, we were waiting for that before we could play the current one, hmmm... what to do?" )
				}
				break;
		}
	},
	
	handleSceneDidNotPreloadEvent : function( event )
	{
		var kDebugHandleSceneDidNotPreloadEvent = "handleSceneDidNotPreloadEvent";
		
		debugMessage( kDebugShowController, kDebugHandleSceneDidNotPreloadEvent, "sceneIndex: " + event.memo.sceneIndex );
		
		var tryAgain = this.promptUserToTryAgain( kUnableToReachiWorkTryAgain );

		if( tryAgain )
		{
			this.textureManager.preloadScene( event.memo.sceneIndex );
		}
		else
		{
		}
		// TODO: handle error condition: retry or enter 
	}
	
})

