/**
 * FlashWrapper.js
 *
 * This is the implementation of the Flash viewer.
 * The viewer is a javascript object that is owned by the brochure object.
 * It interacts with the brochure and the template objects to build up the DOM needed to create a relevant rich content
 * panoramic brochure viewer.  All the communication between the actual FlashMovie or Flash object and the rest of brochure
 * system pass through this object.
 *
 * This version is the implementation of the Flash version.  See AppletWrapper for the Java version.
 *
 * Javascript pseudo-classes defined are:
 *  FlashWrapper - the viewer top level object
 *  FlashMovie - an object used in publishing to represent the overall applet to publish
 *  FlashList - an object used in publishing to represent a list of rooms, layers, actions etc
 *  FlashMovieNode - an object used in publishing to represent a node in a list
 *  FlashScript - an object used in publishing to represent a script action
 *
 * In addition we define the publish methods of DelayedSortList and Callback.
 * publish() is part of an FlashMovie specific publishing system that the flash version knows nothing about.
 * It is the heart of this file.
 *
 * The point is to build up an object network on Javascript and publish the results to an XML string that will be loaded into the running Flash object when it has loaded.
 * The FlashMovie has a publish() method to this effect.
 * The FlashList represents a list of sub-objects called Nodes.
 * For example, the list of Rooms, the list of Layers, the list of Actions and a FlashList of Effects.
 * The FlashMovieNode is always just under a FlashList.
 * The FlashScript represents an Action that calls a sequence of actions.
 *
 * The 2 other types defined globally are:
 * The DelayedSortList is for those lists that are customizable as if they had named properties.
 *     This in turn uses the globally defined DelayedSortNode is a node in such a list.
 * The Callback represents calling back to run some code.
 *
 * The FlashMovieNode namespace holds a number of utility methods:
 * publishSubObj() - Part of the publishing framework to publish any object network even if not constructed with
 * 					FlashMovieNode, FlashList etc.
 * addAction() - Returns either null or the ID of an action object for the given criteria
 * addActionInternal() - Like addAction() only it doesn't return anything because it is given a holder object as in CORBA for Java.
 * addScript() - Adds a script action but otherwise the same as addActionInternal()
 *
 * These use the following from the Utility namespace
 * addIntoNodeN() - Merges a sequence of N data networks into the given node.
 * addIntoListN() - Merges a sequence of N data networks representing lists (eg SeriesEffect.effects) into one list object.
 * doReplacements() - Handles the special embedded expressions language for the Template.
 */

FlashWrapper.prototype.constructor = FlashWrapper;

/**
 * Constructor.
 * Is given a reference to the brochure object.
 */
function FlashWrapper(brochure)
{
		//This is a flag to use an IFrame to pass java to javascript messaging.  On Safari only
	this.useIFrame = this.useIFrame();
		//Back reference to the brochure
	this.brochure = brochure;
		//current page
	this.page = null;
		//current viewable
	this.currentViewable = null;
		//This is an array of objects that contain information on the viewables
	this.viewables = new Object();
		//This contains a list of script functions to add to the window dynamically
	this.callbacks = new Array();
		//The number of callbacks registered
	this.numCallbacks = 0;
		//The parameter tags
	this.paramTags = "";
		//The attributes to add to the applet tag
	this.appletAttributes = "";
		//If callbacks aren't working we do an interogation of the applet
	this.callbacksWorking = false;
		//We keep this just to know what the objects are
	this.room = null; //@ changed from rooms DS20050803
		//Is it muted - only set in the page loading
	this.muteState = "";
}

/**
 * Returns the HTML mark-up to write to the top level outside the brochure div
 */
FlashWrapper.prototype.getInitialExternalContent = function()
{
		//Similar to what will be done for the applet each time
	var applet = new FlashMovie();
	var template = this.brochure.getTemplate();
	var templateData = template.getViewerData('Flash');
	Utility.addIntoNodeN( applet, [
		this.brochure.getCustomizations().viewerObject,	//The brochure can override
		templateData.viewerObject				//Then the template data
		],
		{"brochure":this.brochure},				//Allow anything from the brochure object in replacement expressions
		null,								//No exclusions
		true,								//For publishing (ie not in the applet object)
		this
		);
	this.appletAttributes = applet.getAttributes(this);

	//var str = "<DIV id=\"externalLayer\" style=\"position:fixed;top:0px;left:0px;width:0px;height:0px;overflow:hidden;\">";
	var str = "<DIV id=\"externalLayer\" style=\"position:fixed;top:0px;left:0px;width:1px;height:1px;overflow:visible;visibility:visible;\">";
		//Mayscript is lower case to get around bug in turkish locale
		//On some platforms the only way to pass information from java to javascript is by loading a page into another
		//frame.  The page has to be on the same domain as the script it will call.  We write an IFRAME to be able to
		//do this.
	if( this.useIFrame )
	{
		str+="<DIV id=\"frameHolderLayer\" style=\"position:fixed;top:0px;left:0px;width:1px;height:1px;overflow:hidden;z-index:0;\"><IFRAME id=\"messagePassingFrame\" name=\"messagePassingFrame\" frameborder=\"0\" scrolling=\"no\" marginwidth=\"0\" marginheight=\"0\" width=\"1\" height=\"1\"></IFRAME>";
	}
		//We are using a controller applet and a middle tier controller applet so we write the div tags for those.
		//They have to be "visible" for Firefox to init the applet.  The applet must have one dimension only to get
		//around a bug that is now fixed but not used much in PlugIn 1.4.2 on Windows IE on some versions.
	str+="<DIV id=\"methodPassingLayer\" style=\"position:absolute;top:0px;left:0px;width:1px;height:1px;overflow:hidden;z-index:0;\"></DIV>\n";
	str+="<DIV id=\"brochureAppletLayer\" style=\"position:absolute;top:0px;left:0px;width:1px;height:1px;overflow:hidden;z-index:0;\"></DIV>\n";
	str+="</DIV>";
	return str;
}

/**
 * Called initially as part of page writing to put some initial content into the viewer DIV tag.
 */
FlashWrapper.prototype.getViewerLayerContent = function()
{
	var str = "";
	str+="<DIV id=\"innerAppletLayer\" style=\"position:absolute;z-index:1;width:100%;height:100%;\">";
	str+="</DIV>";
	this.brochure.debug("getViewerLayerContent:\n"+str);
	return str;
}

/**
 * Called on loading a page for example to re-do the viewer layer
 */
FlashWrapper.prototype.formatViewerLayer = function( viewerLayerDivTag, flag, params )
{
	var writeOnce = this.isWriteMainAppletOnce();
	this.brochure.debug( "writeOnce="+writeOnce );

	//Add Flash Callback function
	var singQuot = /\'/g;
	var dubQuot = /\"/g;
	var lineFeed = /\r/g;
	var carriageReturn = /\n/g;
	var xmlstr = '<?xml version="1.0" encoding="iso-8859-1"?>'
	xmlstr+= "<tour>";
	xmlstr+= "<reveal_hotspots>true</reveal_hotspots>";
	xmlstr+= "<bgcolor>#FFFFFF</bgcolor>";
	//xmlstr+= '<STARTROOM>0</STARTROOM><reveal_hotspots>true</reveal_hotspots><room id="0"><image>objects/VRBrochure/flashPage/Example/front2_pano1201.jpg</image></room>'
	xmlstr+= this.paramTags;
	xmlstr+= "</tour>";

	this.brochure.debug( "paramTags='"+xmlstr+"'" );

	xmlstr= xmlstr.replace(singQuot,'');
	xmlstr= xmlstr.replace(dubQuot,'\"');
	xmlstr= xmlstr.replace(lineFeed,'');
	xmlstr= xmlstr.replace(carriageReturn,'');

	var xmlstr2 = '<?xml version="1.0" encoding="iso-8859-1"?>'
	xmlstr2+= this.controllerParamTags;
	xmlstr2= xmlstr2.replace(/-/gi,'%2D');
	xmlstr2= xmlstr2.replace(singQuot,'');
	xmlstr2= xmlstr2.replace(dubQuot,'\"');
	xmlstr2= xmlstr2.replace(lineFeed,'');
	xmlstr2= xmlstr2.replace(carriageReturn,'');
	this.brochure.debug( "this.controllerParamTags='"+xmlstr2+"'" );


	var funstr="";
	//funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" fscommand: '+command+' event recieved. args:'+args);\n";
	funstr+="	switch(command) {\n";
	funstr+="		case 'onZoomChange':\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" fscommand: onZoomChange event recieved. args:'+args);\n";
	//funstr+="			theBrochure.template.notifyZoomChanged(args);\n";
	funstr+="			break;\n";
	funstr+="		case 'onYawChange':\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" DoFSCommand: onYawChange event recieved. args:'+args);\n";
	funstr+="			theBrochure.template.notifyYawChanged(args);\n";
	funstr+="			break;\n";
	funstr+="		case 'onPitchChange':\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" DoFSCommand: onPitchChange event recieved. args:'+args);\n";
	//funstr+="			theBrochure.template.notifyPitchChanged(args);\n";
	funstr+="			break;\n";
	funstr+="		case 'onViewerLoaded':\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" fscommand: onViewerLoaded event recieved. args:'+args);\n";
	funstr+="			document.applet"+this.brochure.id+".SetVariable('xmlscript','"+xmlstr+"');\n";
	funstr+="			break;\n";
	funstr+="		case 'onRoomLoaded':\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" fscommand: onRoomLoaded event recieved. args:'+args);\n";
	funstr+="			theBrochure.notifyViewableChanged(theBrochure.viewer.room[args]);\n";
	funstr+="			theBrochure.buttonClicked('button','left');\n";
	funstr+="			break;\n";
	funstr+="		case 'onBrochureAppletLoaded':\n";
	funstr+="			document.brochureApplet"+this.brochure.id+".SetVariable('xmlscript','"+xmlstr2+"');\n";
	funstr+=" theBrochure.debug( 'document.applet"+this.brochure.id+" fscommand: onBrochureAppletLoaded event recieved. args:'+args);\n";
	funstr+="			break;\n";
	funstr+="	}\n";

	//@MICHAEL ADDED 2005/08/24
	window["DoFSCommand"]=function(command,args){eval(funstr);};

	//Next we clear out and re-write the brochure applet
	var divTag = Brochure.getLayer("brochureAppletLayer");
	if( divTag != null )
	{
		var str = "";
		//str+="<OBJECT mayscript name=\"brochureApplet"+this.brochure.id+"\" height=\"1\" "+this.appletAttributes+" \>\n";
		str+="<OBJECT mayscript id=\"brochureApplet"+this.brochure.id+"\" name=\"brochureApplet"+this.brochure.id+"\" height=\"1\" width=\"1\" "+this.appletAttributes+" \>\n";
		str+="<PARAM name=\"movie\" value=\"BrochureApplet.swf?_applet=appletID"+this.brochure.id+"&_applet=applet"+this.brochure.id+"&panoAppletName=applet"+this.brochure.id+"\" /\>";
		str+="<PARAM name=\"AllowScriptAccess\" value=\"always\" /\>\n";
		str+="<PARAM name=\"panoAppletName\" value=\"applet"+this.brochure.id+"\" /\>\n";
		str+="<PARAM name=\"swliveconnect\" value=\"true\" /\>\n";
		//str+=this.controllerParamTags;
		str+="<embed name=\"brochureApplet"+this.brochure.id+"\" quality=\"high\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" height=\"1\" width=\"1\" swliveconnect=\"true\" AllowScriptAccess=\"always\" ";
		str+="src=\"BrochureApplet.swf\" FlashVars = \"_applet=applet"+this.brochure.id+"&panoAppletName=applet"+this.brochure.id+"";
		str+="\"";
		str+="\></embed\>\n";
		str+="</OBJECT\>\n";

		//str+=jsstr;


		this.brochure.debug( "brochure applet='"+str+"'" );
		divTag.innerHTML = str;

		//if( !writeOnce )
		{

			divTag = Brochure.getLayer("innerAppletLayer");
			if( flag )
			{
				divTag.innerHTML = "";
			}
			str = "";
			var width = Utility.getStyleFromTag( viewerLayerDivTag, "width", "width" );
			if( typeof( width ) == "string" && width.substring( width.length-2 ) == "px" )
			{
				width = width.substring(0,width.length-2);
			}
			var height = Utility.getStyleFromTag( viewerLayerDivTag, "height", "height" );
			if( typeof( height ) == "string" && height.substring( height.length-2 ) == "px" )
			{
				height = height.substring(0,height.length-2);
			}
			// Deliver flash 8 viewer if flash 8 is available
			var hasReqestedVersion = DetectFlashVer(8, 0, 0);
			//alert("stop for debugging");
			if (hasReqestedVersion) {
			//if (0) {
				str+="<OBJECT mayscript classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" id=\"applet"+this.brochure.id+"\" width=\""+width+"\" height=\""+height+"\" "+this.appletAttributes+" \>\n";
				str+="<PARAM name=\"movie\" value=\"PanoViewer8.swf\" /\>\n";
				str+="<PARAM name=\"FlashVars\" value=\"root._width="+width+"&root._height="+height+"\" /\>\n";
				str+="<PARAM name=\"AllowScriptAccess\" value=\"always\" /\>\n";
				str+="<PARAM name=\"swliveconnect\" value=\"true\" /\>\n";
				str+="<embed src=\"PanoViewer8.swf\" name=\"applet"+this.brochure.id+"\" quality=\"high\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" width=\""+width+"\" height=\""+height+"\" swliveconnect=\"true\" AllowScriptAccess=\"always\" \></embed\>\n";
				str+="</OBJECT\>\n";
				this.brochure.debug( "main applet='"+str+"'" );
				divTag.innerHTML = str;
			} else {

				//var hasReqestedVersion = DetectFlashVer(9, 0, 0);
				// Deliver flash 7 viewer
				//if (hasReqestedVersion) {
					str+="<OBJECT mayscript id=\"applet"+this.brochure.id+"\" width=\""+width+"\" height=\""+height+"\" "+this.appletAttributes+" \>\n";
					//str+="<OBJECT mayscript id=\"viewerApplet\" width=\""+width+"\" height=\""+height+"\" "+this.appletAttributes+" \>\n";
					str+="<PARAM name=\"movie\" value=\"PanoViewer7.swf\" /\>\n";
					str+="<PARAM name=\"AllowScriptAccess\" value=\"always\" /\>\n";
					str+="<PARAM name=\"swliveconnect\" value=\"true\" /\>\n";
					str+="<embed src=\"PanoViewer7.swf\" name=\"applet"+this.brochure.id+"\" quality=\"high\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" width=\""+width+"\" height=\""+height+"\" swliveconnect=\"true\" AllowScriptAccess=\"always\" \></embed\>\n";
					str+="</OBJECT\>\n";
					this.brochure.debug( "main applet='"+str+"'" );
					divTag.innerHTML = str;
				//} else {
				//	str+= 'This content requires the Macromedia Flash Player.';
   				//	str+= '<a href=http://www.macromedia.com/go/getflash/>Get Flash</a>';
   				//	this.brochure.debug( "main applet='"+str+"'" );
				//	divTag.innerHTML = str;
				//}
			}

			// call the callback manually because IE doesnt respond first time
			if( writeOnce ) {
				//eval('document.applet'+this.brochure.id).SetVariable("xmlscript",xmlstr);
				//eval('document.brochureApplet'+this.brochure.id).SetVariable("xmlscript",xmlstr2);
				//eval('window.applet'+this.brochure.id+"_DoFSCommand")('onViewerLoaded');
				//eval('window.applet'+this.brochure.id+"_DoFSCommand")('onBrochureAppletLoaded');
				//eval("window.applet"+this.brochure.id+"_DoFSCommand('onBrochureAppletLoaded')");
				window.DoFSCommand('onViewerLoaded');
				window.DoFSCommand('onBrochureAppletLoaded');
			}

		}
	}
	else
	{
		this.brochure.debug("ERROR: Expected brochureAppletLayer to exist");
	}
	for( var callback in this.callbacks )
	{
		var funcVal;		//HACK HACK HACK HELL
		eval("funcVal="+this.callbacks[callback]+";");
		window[callback] = funcVal;
	}
}

FlashWrapper.prototype.isWriteMainAppletOnce = function()
{
	return this.brochure.isIE();
}

/**
 * Called by the termination code to clean up all the remaining stuff from the initial load.
 */
FlashWrapper.prototype.unLoad = function()
{
	var divTag = Brochure.getLayer("viewerLayer");
	divTag.innerHTML = null;
}

/**
 * This is called by the unloadPage mechanism instead of clearing the entire viewerLayer
 */
FlashWrapper.prototype.unloadViewerDiv = function()
{
		//Start by clearing out the controller applet
	var divTag = Brochure.getLayer("methodPassingLayer");
	if( divTag != null )
	{
		divTag.innerHTML = "";
	}
	else
	{
		this.brochure.debug("ERROR: Expected methodPassingLayer to exist");
	}
	var divTag = Brochure.getLayer("brochureAppletLayer");
	if( divTag != null )
	{
		divTag.innerHTML = "";
	}
	else
	{
		this.brochure.debug("ERROR: Expected brochureAppletLayer to exist");
	}
}

/**
 * Called by framework when a page is unloaded (maybe another page will be loaded or maybe the brochure is unloaded next).
 */
FlashWrapper.prototype.unloadPage = function()
{		//Remove the callbacks from the window
	for( var i in this.callbacks )
	{
		if( typeof( window[i] ) == "function" )
		{
			window[i] = null;
		}
	}
}

/**
 * Instructs the wrapper to prepare to display the page.
 * This typically involves a process of writing up an applet tag or flash XML.
 * In this case we already have the main applet written out to the page.
 * What we need is the parameter tags to script up a non-visible applet that tells the main applet to do stuff.
 * It also adds the callback scripts to the web page (window object).
 */
FlashWrapper.prototype.loadPage = function( pageName, page )
{
		//Clear out the room and layer information
	this.room = {}; //@ changed from rooms DS20050803
		//Clear internal representation of them
	this.callbacks = {};
	this.numCallbacks = 0;

	this.soundStarted = false;
	if( page.initMuted )
	{
		this.isMute = true;
	}
	else
	{
		this.isMute = false;
	}

		//We ensure we can find the current viewable
	this.callbacksWorking = false;
		//For speed
	var template = this.brochure.getTemplate();
	var templateData = template.getViewerData('Flash');
		//zoom in etc
	var standardActionNames = template.getViewerActions();
		//Clear the tags
	this.paramTags = "";
	this.controllerParamTags = "";
		//How many call backs are there
	var numCallbacks = 0;
		//Make a fresh applet object
	var applet = new FlashMovie();
		//Create a cache for reducing overhead in the action overhead
	var actionCache = new Array();
		//Clear the list of viewable data objects
	this.viewables = new Array();

		//Default hotspot polygon
	this.hotspotPoints = templateData.hotspotPoints;

		//Setup the top level stuff
	Utility.addIntoNodeN( applet, [
		page.viewerObject,					//Page gets to override all
		this.brochure.getCustomizations().viewerObject,	//The brochure next
		templateData.viewerObject					//Last the Template
		],
		{"brochure":this.brochure},					//Allow anything from the brochure object
		null,										//No exclusions
		true,										//For publishing (ie not in the applet object)
		this
		);
	applet.initMuted = this.isMute;
	applet.onFirstSoundPlaying=new Callback([{code:"function(b){theBrochure.viewer.setFirstSoundPlaying(b);}"}],{},this);
	applet.onMuteStateChanged=new Callback([{code:"function(b){theBrochure.viewer.setMute(b);}"}],{},this);

		//The following levels of customization are allowed:
		//The template defines always at a global level for the viewable type
		//The brochure overrides for the viewable type on the brochure level
		//The brochure overrides for the viewable type on the page level
		//The brochure overrides for the specific viewable
		//
		//The actions though are never overridden on a per-viewable basis.
		//So here we define the map of type to array of brochure customizations
		//based solely on the type of viewable.
		//It is lazily loaded.
	var brochureCustomizations;
	if( page.customizations && page.customizations.viewableTypes )
	{		//And page level is defined - merge them both
		brochureCustomizations = {};
		Utility.addIntoNodeN( brochureCustomizations, [
			page.customizations.viewableTypes,
			this.brochure.getCustomizations().viewableTypes
			], null, null, false, this );					//False means not for publishing
	}
	else
	{		//Only brochure level is defined
		brochureCustomizations = this.brochure.getCustomizations().viewableTypes;
	}

		//First pass is to create the basic applet objects such as rooms.
		//To create the actions for simple things such as hiding and saving, left, right etc
		//Nothing that requires more than one viewable object or room or layer because
		//they're not all created yet.
		//We make objects in the this.viewables array for the ID of the viewable as a key.
		//The object has always the following:
		//	actions - maps the action name to the ID of the action to run in the applet.
		//	thumbnails - maps the viewable ID to the action to run on the thumbnail.
		//	v - The room in the applet that represents it.
	var reps = {};
	for( var id in page.viewables )
	{
			//The data of the specific viewable that comes from brochureData
		var dataV = page.viewables[id];
			//The data from the template for this type of viewable
		var templateDataV = templateData.viewableTypes[dataV.type];
			//Make the representation of the viewable to store us
		var myV = this.viewables[id] = new Array();
		myV.id = id;
			//The actions for us
		myV.action = new Array(); //@ changed from actions DS20050803
			//The thumbnails when we are the current viewable
		myV.thumbnails=new Array();

			//Make the applet based object, put it into variable v and make the reference to it in myV.v
		var roomType;
		roomType = templateDataV.defaultRoomType;
		if( typeof( roomType ) == "string" )
		{
			if (roomType == "SpheriCylinderRoom" || roomType == "pano" || roomType == "CycloRoom") {
				var hasReqestedVersion = DetectFlashVer(8, 0, 0);
				if (hasReqestedVersion) {
				//if (0) {
					 roomType = "3dpanorama"; //3dpanorama
				} else {
					 roomType = "pano";
				}
			}
		}

		//This is the set of tags being prepared
		var v = myV.v = applet.room.addNode(roomType);  //@ changed from rooms DS20050803
		this.room[v.indexInList] = id; //@ changed from rooms DS20050803

			//Add customizers
			//Here is the replacements map.
		reps.v = myV;
		reps.type = dataV.type;
			//Add the customizations in
		var datas;
		if( brochureCustomizations != null &&
			brochureCustomizations[dataV.type] &&
			typeof( brochureCustomizations[dataV.type].extra ) != "undefined" &&
			brochureCustomizations[dataV.type].extra != null )
		{
			datas = [dataV, brochureCustomizations[dataV.type].extra, templateDataV.extra];
		}
		else
		{
			datas = [dataV, templateDataV.extra];
		}
		Utility.addIntoNodeN( v, datas, reps, templateDataV.excludeProps, true, this );

			//Add the hide and show actions
			//null here signifies that the brochureData can not override anything
		myV.action.hide = FlashMovieNode.addAction( applet.action, null, templateData, "hide", actionCache, reps, false ); //@ changed from actions DS20050803
		myV.action.show = FlashMovieNode.addAction( applet.action, null, templateData, "show", actionCache, reps, false ); //@ changed from actions DS20050803

			//Add the showOff action to the thumbnails
		myV.thumbnails[id] =
			FlashMovieNode.addAction( applet.action, brochureCustomizations, templateData, "showOff", actionCache, reps, false ); //@ changed from actions DS20050803
			//Add the standard actions in - left, right, zoom etc
		for( id in standardActionNames )
		{
			var actionName = standardActionNames[id];
			if( actionName != "stop" && actionName != "guidedTour" )
			{
				myV.action[actionName] = FlashMovieNode.addAction( applet.action, brochureCustomizations, templateData, actionName, actionCache, reps, false ); //@ changed from actions DS20050803
			}
		}
	}

			//Second pass is to build up the transition actions between the viewables and the hotspots that link to external sites.
			//If a pano in outer loop, we do the hotspot configurations.
			//In second loop we only do those whose thumbnail transitions aren't yet there.

			//Replacement parameters
	var reps = {};
	for( var id in page.viewables )
	{
			//This is the original data object for the viewable
		var dataV = page.viewables[id];
			//This is our copy of the object
		var myV = this.viewables[id];

		if( typeof( dataV.hotspots ) == "object" && dataV.hotspots != null )
		{		//If it has hotspots, then we do the hotspots first before thumbnails.
				//This is done by cycling the hotspots and calling "hotspot" action or "hotspotToURL" action.
				//In the first case it is a hotspot to another viewable so we call "hotspot" on the target viewable.
				//In the second case we call it on the current viewable.
				//
				//On the target viewable passing the replacement parameters:
				// fromV - the pano we're on.
				// v - the target viewable.
				// type - the type of the target viewable.
				// hs - the hotspot in the applet.
				//
				//For external URLs, on the current viewable passing the replacement parameters:
				// hs - the hotspot in the applet.
				//
				//Ensure that even if we have no hotspots, we add this in so the delete at the end doesn't fail
			reps.hs=null;
				//Fill other global replacement parameters
			reps.page = page;
			reps.viewable = this;
				//This is the brochureData representation of the hotspot.
			var dataHotspots = dataV.hotspots;
				//The type of hotspot to use
			var defaultHotspotType = dataV.hotspotType ? dataV.hotspotType : templateDataV.defaultHotSpotType;
				//Create the list of hotspots for the applet object
			var hotspot = myV.v.hotspot = new FlashList(defaultHotspotType);
				//This maps the viewable name to the hotspot id
			myV.hotspot = {};
				//The type we're on
			var fromType=dataV.type;
			var hsTemplateData = templateData.viewableTypes[fromType].allHotspots;
					//Loop the hotspots
			for( var hsid in dataHotspots )
			{		//Get the data for the hotspot and make a new applet based hotspot
				var dataHotspot = dataHotspots[hsid];
				hotspotType = dataHotspot.hotspotType ? dataHotspot.hotspotType : dataV.hotspotType ? dataV.hotspotType : defaultHotspotType;
				var aHotspot = hotspot.addNode(hotspotType);
					//Fill in replacements common to both types of hotspot
				reps.hs = aHotspot;
				reps.dataHS = dataHotspot;

                        if( typeof(dataHotspot.targetViewableName) == "string" )
                        //if(1)
				{		//Going from one viewable to another viewable
					var toID = dataHotspot.targetViewableName;
						//Put the mapping for linking from this viewable to the other one into the hotspots array
					myV.hotspot[toID]=aHotspot.indexInList;
						//Get the data from the brochure for the "to" viewable
					var dataToV = page.viewables[toID];

						//Set up the replacements so that the current viewable is NOT the logical "this" reference.
						//Instead the viewable we are going TO is the logical "this" reference.
					reps.v = this.viewables[toID];	//this - Java equivalent
					reps.type = dataToV.type;		//this.getClass() - Java equivalent
						//Here we set up the fromV - deleted always at the end of this if statement
						//This is used as the "from" parameter
					reps.fromV=myV;
					reps.fromType=fromType;
						//This is used to lookup caching
					reps.cacheKey=reps.v.id+"_"+reps.fromV.id;

						//Work out the set of overlaid data contexts:
						//1) The data for the specific hotspot definition.
						//2) The brochure's to viewable's type can define more things
						//3) The template's viewable's type can define more things
						//4) The brochure's <fromType>.allHotspots can define more things
						//5) The template's <fromType>.allHotspots defines the most obvious things
					var datas = [];
					datas.push(dataHotspot);
					var step4 = null;
					if( brochureCustomizations != null )
					{		//We have the brochure data customizing things
						if( brochureCustomizations[dataToV.type] && brochureCustomizations[dataToV.type]["hotspotExtras"] )
						{		//TODO determine why this is on the TO viewable
							datas.push( brochureCustomizations[dataToV.type]["hotspotExtras"] );
						}
						if( brochureCustomizations[fromType] && brochureCustomizations[fromType].allHotspots && brochureCustomizations[fromType].allHotspots["extras"] )
						{		//The brochure customizes the overall hotspot definition
							step4 = brochureCustomizations[fromType].allHotspots["extras"];
						}
					}
					if( templateData.viewableTypes[dataToV.type]["hotspotExtras"] )
					{
						datas.push(templateData.viewableTypes[dataToV.type]["hotspotExtras"] );
					}
					if( step4 != null )
					{
						datas.push( step4 );
					}
					if( templateData.viewableTypes[fromType].allHotspots && templateData.viewableTypes[fromType].allHotspots["extras"] )
					{
						datas.push( templateData.viewableTypes[fromType].allHotspots["extras"] );
					}
					Utility.addIntoNodeN( aHotspot, datas, reps, hsTemplateData.excludeProps, true, this );
					aHotspot.action=
						FlashMovieNode.addAction( applet.action, brochureCustomizations, templateData, "hotspotNormal", actionCache, reps, false ); //@ changed from actions DS20050803

					delete reps.fromV;
					delete reps.cacheKey;
				}
				else {
						//Fill in the replacements
					reps.v = myV;		//this - Java equivalent
					reps.type = dataV.type;	//this.getClass() - Java equivalent
						//This is used to lookup caching
					///@reps.cacheKey=reps.v.id+"_hotspot_"+hsid;
						
						//Here we set up the fromV - deleted always at the end of this if statement
						//This is used as the "from" parameter
					reps.fromV=myV;
					reps.fromType=fromType;
						//This is used to lookup caching
					reps.cacheKey=reps.v.id+"_"+reps.fromV.id;

						//Work out the set of overlaid data contexts:
						//1) The data for the specific hotspot definition.
						//2) The brochure's viewable's type can define more things
						//3) The template's viewable's type can define more things
						//4) The brochure's <fromType>.allHotspots can define more things
						//5) The template's <fromType>.allHotspots defines the most obvious things
					var datas = [];
					datas.push(dataHotspot);
					var step4 = null;
					if( brochureCustomizations != null )
					{		//We have the brochure data customizing things
						if( brochureCustomizations[dataV.type] && brochureCustomizations[dataV.type]["hotspotExtras"] )
						{
							datas.push( brochureCustomizations[dataV.type]["hotspotExtras"] );
						}
						//if( brochureCustomizations[fromType] && brochureCustomizations[fromType].allHotspots && brochureCustomizations[fromType].allHotspots.extra )
						if( brochureCustomizations[fromType] && brochureCustomizations[fromType].allHotspots && brochureCustomizations[fromType].allHotspots["extras"] )
						{		//The brochure customizes the overall hotspot definition
							//@step4 = brochureCustomizations[fromType].allHotspots.extra;
							step4 = brochureCustomizations[fromType].allHotspots["extras"];
						}
					}
					if( templateData.viewableTypes[dataV.type]["hotspotExtras"] )
					{
						datas.push(templateData.viewableTypes[dataV.type]["hotspotExtras"] );
					}
					if( step4 != null )
					{
						datas.push( step4 );
					}
					if( templateData.viewableTypes[fromType].allHotspots && templateData.viewableTypes[fromType].allHotspots["extras"] )
					{
						datas.push( templateData.viewableTypes[fromType].allHotspots["extras"] );
					}
					var defText = "";
					if( typeof(dataHotspot.url) == "string" )
					{		//Link to an external URL
						reps.url = dataHotspot.url;
						defText = dataHotspot.url;
					}
					if (typeof(dataHotspot.text) == "undefined") {
						aHotspot.text = defText;
					}
					Utility.addIntoNodeN( aHotspot, datas, reps, hsTemplateData.excludeProps, true, this );
					if( typeof(dataHotspot.url) == "string" )
					{		//Link to an external URL
						aHotspot.action=
							FlashMovieNode.addAction( applet.action, brochureCustomizations, templateData, "hotspotToURL", actionCache, reps, false ); //@ changed from actions DS20050803
						delete reps.url;
					}
					delete reps.cacheKey;
					delete reps.url;
				}
			}
			delete reps.hs;
		}
		for( var toID in page.viewables )
		{		//Loop all the viewables again and do the transitions from us to the other viewable
			if( toID != id )
			{		//Not to ourselves - make a thumbnail action between this viewable and the to viewable
					//Determine if there is a hotspot
				if( myV.hotspot && typeof( myV.hotspot[toID] ) == "number" )
				{		//There is a hotspot, so we call thumbnailWithHotspot
						//This is called on the TO and not the FROM viewable.
					reps.v = this.viewables[toID];
					reps.type = page.viewables[toID].type;
					reps.fromV = myV;
					reps.fromType = dataV.type;
						//Put the reference to the hotspot in
					reps.hs = myV.v.hotspot[myV.hotspot[toID]];
						//So it can find previously made actions easily
					reps.cacheKey=reps.v.id+"_"+reps.fromV.id;
						//Make a call to thumbnailWithHotspot on the origin viewable
					myV.thumbnails[toID] = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "thumbnailWithHotspot", actionCache, reps, false ); //@ changed from actions DS20050803

						//Remove the parameter
					delete reps.hs;
					delete reps.fromV;
					delete reps.fromType;
				}
				else
				{		//No hotspot - just normal thumbnail action
					reps.v = myV;
					reps.type = dataV.type;
					reps.toV = this.viewables[toID];
					reps.toType = page.viewables[toID].type;
					reps.cacheKey=reps.v.id+"_"+reps.toV.id;
					myV.thumbnails[toID] = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "thumbnail", actionCache, reps, false ); //@ changed from actions DS20050803
					delete reps.toV;
					delete reps.toType;
				}
			}
		}
	}
		//Third pass is to build the guided tour action and then build in all the actions that call it
		//The guided tour starts at the viewable you're in now.
		//But the real beginning could be somewhere else.
		//So the first thing to do is to run an action to move to the initial viewable.
		//The second thing is to build up the list of viewables in order and make them show off,
		//move to the next one and so on until the end.
		//
		//The sequence that is repeated is therefore either tourTransition or tourTransitionWithHotspot
		//
	if( page.guidedTour.num > 0 )
	{
			//We build up the list of sequences of tourTransition and tourTransitionWithHotspot first
		var dataFromV = null;	//The previous viewable data
		var myFromV = null;		//The previous viewable
		var actionsList = null;	//Will end up as a list of action IDs beginning with a comma
		reps = [];
		for( var id = 0; id < page.guidedTour.num; id++ )
		{		//Loop the viewables in the guided tour in order
			var vid = page.guidedTour[id];
				//Get the viewable data object
			var dataV = page.viewables[vid];
				//Get my version of it
			var myV = this.viewables[vid];
			var trans;
			reps.v = myV;
			reps.type = dataV.type;
				//NB In all addAction calls here we pass 'true'	as the final argument because we can get back a list of action IDs
			if( myFromV == null )
			{		//First viewable
				trans = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "showOffTour", actionCache, reps, true ); //@ changed from actions DS20050803
			}
			else
			{		//Not the first viewable in the guided tour
				reps.fromV = myFromV;
				reps.fromType = dataFromV.type;
				reps.cacheKey = myV.id+"_"+myFromV.id;
				if( myFromV.hotspots && typeof( myFromV.hotspots[vid] ) == "number" )
				{		//Has a hotspot
					reps.hs = myFromV.v.hotspots[myFromV.hotspots[vid]];
					trans = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "tourTransitionWithHotspot", actionCache, reps, true ); //@ changed from actions DS20050803
					delete reps.hs;
				}
				else
				{
					trans = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "tourTransition", actionCache, reps, true ); //@ changed from actions DS20050803
				}
					//No need to delete fromV, fromType, cacheKey because they will exist for every turn now on
			}
				//Add to the action list
			if( trans != null )
			{
				if( actionsList != null )
				{
					actionsList += ","+trans;
				}
				else
				{
					actionsList = trans;
				}
			}
				//Age the previous values
			myFromV = myV;
			dataFromV = dataV;
		}
			//Now go through every single viewable and add a guided tour for it
		var startID = page.guidedTour[0];
		reps = [];
		for( var id in page.viewables )
		{			//prefix will store the actionID(s) for the prefix action(s)
			var prefix = null;
			if( id == startID )
			{		//Found the start of the guided tour - use tourInit
				reps.v = this.viewables[id];
				reps.type = page.viewables[id].type;
				prefix = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "tourInit", actionCache, reps, true ); //@ changed from actions DS20050803
			}
			else
			{
				reps.fromV = this.viewables[id];
				reps.fromType = page.viewables[id].type;
				reps.v = this.viewables[startID];
				reps.type = page.viewables[startID].type;
				reps.cacheKey = reps.v.id+"_"+reps.fromV.id;
				if( reps.fromV.hotspots && typeof( reps.fromV.hotspots[id] ) == "number" )
				{		//Not start, so do standard transition and then start it
					reps.hs = reps.fromV.v.hotspots[reps.fromV.hotspots[id]];
					prefix = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "tourStartWithHotspot", actionCache, reps, true ); //@ changed from actions DS20050803
					delete reps.hs;
				}
				else
				{
					prefix = FlashMovieNode.addAction( applet.action, null/*TODO*/, templateData, "tourStart", actionCache, reps, true ); //@ changed from actions DS20050803
				}
				delete reps.fromV;
				delete reps.fromType;
			}
				//This will store the entire action ID list for the guided tour from here
			var guidedTourAction;
			if( prefix == null )
			{
				guidedTourAction=""+actionsList;
			}
			else if( actionsList == null )
			{
				guidedTourAction=""+prefix;
			}
			else
			{
				guidedTourAction=prefix+","+actionsList;
			}
				//Now we add it in
			if( guidedTourAction == null || guidedTourAction.indexOf(",") == -1 )
			{
				this.viewables[id].action.guidedTour = guidedTourAction;
			}
			else
			{
				var node = applet.action.addNode("Script"); //@ changed from actions DS20050803
				node.loop=false;
				node.interruptable=true;
				node.actions=guidedTourAction; //@ changed from actions DS20050803
				this.viewables[id].action.guidedTour = node.indexInList;
			}
		}
	}
		//Setup the first viewable that we see
	var myV = this.viewables[page.currentViewableName];
	applet.startroom = myV.v.indexInList;
		//Setup the initial action
	//applet.room[applet.startroom].initial_action = myV.thumbnails[page.currentViewableName]; //@ changed from rooms DS20050803

		//Remove the v sub objects, hotspot sub objects etc
	for( var id in this.viewables )
	{
		var obj = this.viewables[id];
		for( var j in obj )
		{
			if( j != "action" && j != "thumbnails" ) //@ changed from actions DS20050803
			{
				delete this.viewables[id][j];
			}
		}
	}
		//Build the controller tags
	var str = "";
	var max = -1;
	for( var i in this.room )
	{
		if( max < (1*i) )
		{
			max = (1*i);
		}
		str+="<brochure.rooms["+i+"]>"+escape(this.room[i])+"</brochure.rooms["+i+"]>\n";
	}
	//str+="<brochure.rooms.length>"+(1+1*max)+"</brochure.rooms.length>\n";
	for( var id in this.viewables )
	{
		var obj = this.viewables[id];
		for( var j in obj.action ) //@ changed from actions DS20050803
		{
			var val = obj.action[j]; //@ changed from actions DS20050803
			if( val != null )
			{
				str+="<brochure.actions["+escape(id)+"]["+escape(j)+"]>"+val+"</brochure.actions["+escape(id)+"]["+escape(j)+"]>\n";
			}
		}
		for( var j in obj.thumbnails )
		{
			var val = obj.thumbnails[j];
			if( val != null )
			{
				str+="<brochure.thumbnails["+escape(id)+"]["+escape(j)+"]>"+val+"</brochure.thumbnails["+escape(id)+"]["+escape(j)+"]>\n";
			}
		}
	}
	this.controllerParamTags = str;
	if( this.useIFrame )
	{		//Note that callbackURL should be stored in the template definition
		this.paramTags = "<callbackFrame>messagePassingFrame</callbackFrame>";
	}
	else
	{
		this.paramTags = "";
	}
	this.paramTags += applet.publish(this);
	this.currentViewable = page.currentViewableName;
}


/**
 * Callback from applet.onLoad so we know the callbacks are working
 */
FlashWrapper.prototype.setCallbacksWorking = function()
{
	this.brochure.debug("Callbacks are working");
	this.callbacksWorking = true;
}

/**
 * Instruct the applet to show the specified viewable.
 * If it succeeded then return true, else false.
 */
FlashWrapper.prototype.showViewable = function(id)
{
	if( this.rewriteControllerApplet("thumbnail",id) )
	{
		if( !this.callbacksWorking )
		{		//TODO - ensure this makes sense
			this.brochure.notifyViewableChanged(id);
		}
		return true;
	}
	return false;
}

FlashWrapper.prototype.notifyViewableChanged = function( id )
{
	this.currentViewable = id;
}

/**
 * Tells the applet to do some action.
 * id is a named action.
 */
FlashWrapper.prototype.doAction = function( id )
{
	if( id == "stop" )
	{
		return this.rewriteControllerApplet("stop",null);
	}
	else if( id == "mute" )
	{
		return this.rewriteControllerApplet("mute",null);
	}
	else
	{
		return this.rewriteControllerApplet("action",id);
	}
}

/**
 * Returns whether the action is there or not (true or false or null).
 * true means that it is there and valid.
 * false means that it is there and not valid.
 * null means that it is not there.
 *
 * This method is used by Template.resetButtonStates.
 * If the result is not null it sets the enabled state of the button to the returned boolean.
 */
FlashWrapper.prototype.getButtonState = function( id )
{
	var actions = this.viewables[this.currentViewable].action; //@ changed from actions DS20050803
	if( typeof( actions[id] ) != "undefined" )
	{
		return actions[id] != null;
	}
	return null;
}

/**
 * Returns the id/name of the current viewable.
 * @param - search If true we find out what the current viewable is, otherwise we
 * return what we think, but are not sure, it is.
 * TODO this fails in Netscape with the plugin due to the lack of Javascript to Java working properly!!!!
 */
FlashWrapper.prototype.getCurrentViewable = function(search)
{
	if( this.callbacksWorking || !search )
	{
		return this.currentViewable;
	}
	var ans = document.applets.applet.jsGetActiveRoomID();
	return this.room[ans]; //@ changed from rooms DS20050803
}

/**
 * Callback from Callback.publish( context, id ).
 */
FlashWrapper.prototype.addCallback = function( code )
{
	var callbackNum = this.numCallbacks++;
	var val = "callback"+callbackNum;
	this.callbacks[val]=code;
	return val;
}

/**
 * FlashMovie.
 * The applet is the root of the publishing network for applet param tags.
 */
function FlashMovie()
{
	var hasReqestedVersion = DetectFlashVer(8, 0, 0);
	if (hasReqestedVersion) {
	//if (0) {
		 this.room = new FlashList("3dpanorama"); //3dpanorama	//@ changed from rooms DS20050803
	} else {
		 this.room = new FlashList("pano");			//@ changed from rooms DS20050803
	}
	this.layers = new FlashList("");
	this.action = new FlashList("");
}

/**
 * Publishing the applet returns the string of parameter tags for this applet.
 */
FlashMovie.prototype.publish = function(context)
{
	var str="";
	for( var i in this )
	{
		var obj = this[i];
		if( typeof( FlashMovie.prototype[i] ) == "undefined" && i != "attributes" )
		{		//Not part of the definition and not attributes list, then we publish it
			if( typeof( obj ) == "object" )
			{
				if( typeof( obj.publish ) == "function" )
				{
					str+=obj.publish( context,i );
				}
				else
				{
					str+=FlashMovieNode.publishSubObj( context,i, obj );
				}
			}
			else
			{
				str+="<"+i+">"+obj+"</"+i+">\n";
			}
		}
	}
	return str;
}

/**
 * Returns the attributes of the applet excluding the code, width, height and name, archive.  TODO archive etc
 * This is typically the codebase and archive only.
 */
FlashMovie.prototype.getAttributes = function(context)
{
	if( this.attributes )
	{
		var str = "";
		for( var i in this.attributes )
		{
			if (i != "codebase" || this.attributes[i] != "") {
				str+=" "+i+"=\""+this.attributes[i]+"\"";
			}
		}
		return str;
	}
	return "";
}

/**
 * FlashList
 */
function FlashList(listType)
{
	this.nNodes=0;
	this.listType = listType;
}

/**
 * Adds a node into a list of the given type.
 * This builds an FlashMovieNode object that has its own publish method.
 * The type is written out as a parameter at the level of the stub.
 */
FlashList.prototype.addNode=function(nodeType)
{
	return this[this.nNodes]=new FlashMovieNode(nodeType,this.nNodes++);
}

/**
 * Publishing a list , unlike the applet the flash viewer doesnt need to know .length of a list
 */
FlashList.prototype.publish=function( context, id )
{
	var str = "";
	var n = this.nNodes;
	//str+="<"+id+".length>"+n+"</"+id+".length>\n";
	for( var i = 0; i < n; i++ )
	{
		var node = this[i];
		//var subID = id+"["+i+"]";
		var subID = id+" id=\""+i+"\"";
		if( typeof( node.nodeType ) == "string")// && node.nodeType != this.listType)
		{
			str+="<"+subID+" type=\""+node.nodeType+"\">\n";
			str+="	<type>"+node.nodeType+"</type>\n";
		}
		else {
			str+="<"+subID+">\n";
		}
		str+=node.publish(context,subID);
		str+="</"+id+">\n";
	}
	return str;
}

/**
 * ----
 * FlashMovieNode
 * ----
 * A FlashMovieNode is an object in a FlashList within the FlashMovie.
 * It has a nodeType and an indexInList.
 * These two properties are handled by the FlashList.
 *
 * It has one method - publish().  This is called as part of the publishing framework.
 */
function FlashMovieNode( nodeType, indexInList )
{
	this.nodeType = nodeType;
	this.indexInList = indexInList;
}

/**
 * This is called by the FlashList object as part of publishing
 */
FlashMovieNode.prototype.publish=function( context, id )
{
	var str="";
	for( var i in this )
	{
		if( i != "nodeType" && i != "indexInList" )
		{
			var obj = this[i];
			if( typeof( obj ) == "object" )
			{
				if( obj != null )
				{
					if( typeof( obj.publish ) == "function" )
					{
						//str+=obj.publish( context,id+"."+i );
						str+=obj.publish( context,i );
					}
					else
					{
						//str+=FlashMovieNode.publishSubObj( context, id+"."+i, obj );
						str+=FlashMovieNode.publishSubObj( context, i, obj );
					}
				}
			}
			else if( i != 'publish' )
			{
				//str+="<"+id+"."+i+">"+obj+"</"+id+"."+i+">\n";
				str+="<"+i+">"+obj+"</"+i+">\n";
			}
		}
	}
	return str;
}

/**
 * Static method that forms part of the publishing framework.
 * id - The parameter stub of this object.
 * obj - an object to publish (not a FlashMovieNode and not a FlashList).
 */
FlashMovieNode.publishSubObj = function( context, id, obj )
{
	if( typeof( obj.nodeType ) == "string" && obj.nodeType == "Resource" )
	{
		return FlashMovieNode.publishResource( context, id, obj );
	}
	else
	{
		var str = "";
		for( var i in obj )
		{
			var val = obj[i];
			if( i == "nodeType" )
			{
				str+="<"+id+">"+val+"</"+id+">\n";
			}
			else if( typeof( val ) == "object" )
			{
				if( typeof( val["publish"] ) == "function" )
				{
					//str+=val.publish( context,id+"."+i );
					str+=val.publish( context,i );
				}
				else
				{
					//str+=FlashMovieNode.publishSubObj( context,id+"."+i, val );
					str+=FlashMovieNode.publishSubObj( context,i, val );
				}
			}
			else
			{
				//str+="<"+id+"."+i+">"+val+"</"+id+"."+i+">\n";
				str+="<"+i+">"+val+"</"+i+">\n";
			}
		}
		return str;
	}
}

/**
 * Resource nodes are not even a separate js type, they have a hook here to re-write their url.
 */
FlashMovieNode.publishResource = function( context, id, obj )
{
	var str = "";
	for( var i in obj )
	{
		switch( i )
		{
		case "nodeType":break;
		case "url":
			str+="<"+id+">"+obj.url+"</"+id+">\n";
			break;
		default:
			var val = obj[i];
			if( typeof( val ) == "object" )
			{
				if( typeof( val["publish"] ) == "function" )
				{
					//str+=val.publish( context,id+"."+i );
					str+=val.publish( context,i );
				}
				else
				{
					//str+=FlashMovieNode.publishSubObj( context,id+"."+i, val );
					str+=FlashMovieNode.publishSubObj( context,i, val );
				}
			}
			else
			{
				//str+="<"+id+"."+i+">"+val+"</"+id+"."+i+">\n";
				str+="<"+i+">"+val+"</"+i+">\n";
			}
		}
	}
	return str;
}

/**
 * Adds an action to the actions list, either getting it from the cache or adding it to the cache if possible.
 * For example 'showOff'.
 * @param actions - the applet.actions list
 * @param dataHolder - either null or a holder for type based lookups of brochure customizations.
 *  Typically this is a merging of various sources of data so we can do dataHolder['pano'].actions.
 * @param templateDataHolder - the top level template data object.
 *  We can lookup using templateDataHolder.viewableTypes['pano'].actions and if that doesn't work, templateDataHolder.globalActions.
 *  Note that the dataHolder can not define the type of action, that must come from the template.
 * @param actionName - The name of the action.
 * @param cache - The cache to lookup and store the action.
 * @param replacements - The name/value pairs for replacements.
 * @param inScript - is this action inside a script?
 */
FlashMovieNode.addAction = function( actions, dataHolder, templateData, actionName, cache, replacements, inScript )
{
	var holder = {cachable:true,id:null};
	var temp = false;
	if( typeof( replacements.cacheKey ) == "undefined" && typeof( replacements.v ) == "object" && typeof( replacements.v.id ) != "undefined" )
	{
		replacements.cacheKey = replacements.v.id;
		temp = true;
	}
	FlashMovieNode.addActionInternal( actions, holder, [], dataHolder, templateData, actionName, cache, replacements, inScript );
	if( temp )
	{
		delete replacements.cacheKey;
	}
	return holder.id;
}

/**
 * To save time and memory we don't recreate this every timie
 */
FlashMovieNode.NODE_TYPE = {"nodeType":true};

/**
 * This method will find an action or create one or store null.
 * The information is placed in the holder object and it does not return anything.
 * The structure of the code is as follows:
 * 1) Find in cache.  This is for actions that can be used by any viewable of the same type such as a standard Pano Left.
 * 2) Lookup if the action is in the viewable's standard actions.  This fails if there is no viewable.
 * 3) It then starts adding object networks to a stack of such so that the action can be created later.
 *    It has three sources for these:
 *    a) The brochureData
 *    b) The template's viewer specific action definitions
 *    c) The template's global actions
 *    The code searches each of these places in exactly the same way:
 *    If there is a definition of the action, we stop and process and return, otherwise push the context to the stack.
 *
 * A definition is defined as:
 * 1) null - The action is forced not to exist.  This overrides any other customizations.
 * 2) it finds a nodeType that is a string.
 * 3) It finds that the definition object is actually a string, in which case it uses this as a delegate.
 *
 * The nodeType is checked if it is FlashScript and if so addScript() is called.
 * Otherwise a new FlashMovieNode is created and addIntoNodeN() is called passing the stack of object networks to merge in.
 */
FlashMovieNode.addActionInternal = function( actions, holder, datas, dataHolder, templateData, actionName, cache, replacements, inScript )
{
	var vType = replacements.type;
	var cacheKey = vType+actionName;
	if( typeof( cache[cacheKey] ) != "undefined" )
	{		//Use the cache
		holder.id=cache[cacheKey];
		return;
	}
		//We should always have a second cache key which is ALWAYS used
		//However just in case, we check first
	var cacheKey2 = null;
	if( typeof( replacements["cacheKey"] ) != "undefined" )
	{
		cacheKey2 = replacements["cacheKey"]+actionName;
		if( typeof( cache[cacheKey2] ) != "undefined" )
		{		//If it was in second, but not first then it isn't cachable
			holder.id = cache[cacheKey2];
			holder.cachable = false;
			return;
		}
	}
	var data = null;
	if( dataHolder != null && dataHolder[vType] && typeof( dataHolder[vType].action ) == "object" ) //@ changed from actions DS20050803
	{
		data = dataHolder[vType].action; //@ changed from actions DS20050803
	}
	if( data != null )
	{
		switch( typeof( data[actionName] ) )
		{
		case "string":
				//Delegation counts as a definition of an action - we drop everything and forward
			var temp = {};
			var dataActionName = data[actionName];
			if( Utility.doReplacements( replacements, dataActionName, temp, "tempVal") )
			{
				dataActionName = temp.tempVal;
				FlashMovieNode.addActionInternal( action, holder, datas, dataHolder, templateData, dataActionName, cache, replacements, inScript ); //@ changed from actions DS20050803
				holder.cachable = false;
			}
			else
			{
				FlashMovieNode.addActionInternal( action, holder, datas, dataHolder, templateData, dataActionName, cache, replacements, inScript ); //@ changed from actions DS20050803
				if( holder.cachable )
				{
					cache[cacheKey] = holder.id;
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
			}
			return;
		case "object":
			if( data[actionName] == null )
			{		//Data says cancel
				cache[cacheKey] = null;
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
				return;
			}
			if( typeof( data[actionName].nodeType ) == "string" )
			{		//Data defines the action type
				if( data[actionName].nodeType == "Script" )
				{
					FlashMovieNode.addScript( action, holder, dataHolder, templateData, data[actionName], actionName, cache, vType, replacements, inScript ); //@ changed from actions DS20050803
					if( holder.cachable )
					{
						cache[cacheKey] = holder.id;
					}
				}
				else
				{
					datas.push( data[actionName] );
					var theAction = action.addNode(data[actionName].nodeType); //@ changed from actions DS20050803
					if( Utility.addIntoNodeN( theAction, datas, replacements, FlashMovieNode.NODE_TYPE, true, FlashWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = theAction.indexInList;
					}
					else
					{
						holder.id = theAction.indexInList;
						holder.cachable = false;
					}
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
				return;
			}
			datas.push( data[actionName] );
			break;
		default:		//Should be undefined - legal
			break;
		}
	}

	if( templateData.viewableTypes[vType] && typeof( templateData.viewableTypes[vType].actions[actionName] ) != "undefined" )
	{		//Got a match in the viewable type
		var templateActionDef = templateData.viewableTypes[vType].actions[actionName];
		switch( typeof( templateActionDef ) )
		{
		case "string":
				//Delegation counts as a definition of an action - we drop everything and forward
			var temp = {};
			if( Utility.doReplacements( replacements, templateActionDef, temp, "tempVal") )
			{
				templateActionDef = temp.tempVal;
				FlashMovieNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				holder.cachable = false;
			}
			else
			{
				FlashMovieNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				if( holder.cachable )
				{
					cache[cacheKey] = holder.id;
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
			}
			return;
		case "object":
			if( templateActionDef == null )
			{		//Data says cancel
				cache[cacheKey] = null;
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = null;
				}
				return;
			}
			if( typeof( templateActionDef.nodeType ) == "string" )
			{		//Data defines the action type
				if( templateActionDef.nodeType == "Script" )
				{
					FlashMovieNode.addScript( actions, holder, dataHolder, templateData, templateActionDef, actionName, cache, vType, replacements, inScript );
					if( holder.cachable )
					{
						cache[cacheKey] = holder.id;
					}
					if( cacheKey2 != null )
					{
						cache[cacheKey2] = holder.id;
					}
				}
				else
				{
					datas.push( templateActionDef );
					var theAction = actions.addNode(templateActionDef.nodeType);
					if( Utility.addIntoNodeN( theAction, datas, replacements, FlashMovieNode.NODE_TYPE, true, FlashWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = theAction.indexInList;
					}
					else
					{
						holder.id = theAction.indexInList;
						holder.cachable = false;
					}
					if( cacheKey2 != null )
					{
						cache[cacheKey2] = holder.id;
					}
				}
				return;
			}
			datas.push( templateActionDef );
			break;
		default:		//Should be undefined - legal
			break;
		}
	}
	if( templateData.globalActions && typeof( templateData.globalActions[actionName] ) != "undefined" )
	{		//Get a match in the global actions
		templateActionDef = templateData.globalActions[actionName];
		switch( typeof( templateActionDef ) )
		{
		case "string":
				//Delegation counts as a definition of an action - we drop everything and forward
			var temp = {};
			if( Utility.doReplacements( replacements, templateActionDef, temp, "tempVal") )
			{
				templateActionDef = temp.tempVal;
				FlashMovieNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				holder.cachable = false;
			}
			else
			{
				FlashMovieNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				if( holder.cachable )
				{
					cache[cacheKey] = holder.id;
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
			}
			return;
		case "object":
			if( templateActionDef == null )
			{		//Data says cancel
				cache[cacheKey] = null;
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = null;
				}
				return;
			}
			if( typeof( templateActionDef.nodeType ) == "string" )
			{		//Data defines the action type
				if( templateActionDef.nodeType == "Script" )
				{		//A script overrides all - we ignore all the stuff above us in datas
					FlashMovieNode.addScript( actions, holder, dataHolder, templateData, templateActionDef, actionName, cache, vType, replacements, inScript );
					if( holder.cachable )
					{
						cache[cacheKey] = holder.id;
					}
				}
				else
				{
					datas.push( templateActionDef );
					var theAction = actions.addNode(templateActionDef.nodeType);
					if( Utility.addIntoNodeN( theAction, datas, replacements, FlashMovieNode.NODE_TYPE, true, FlashWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = theAction.indexInList;
					}
					else
					{
						holder.id = theAction.indexInList;
						holder.cachable = false;
					}
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
				return;
			}
			datas.push( templateActionDef );
			break;
		default:		//Should be undefined - legal
			break;
		}
	}
}

/**
 * Adding a FlashScript is different because we callback to addAction().
 * actions - the applet.actions list of actions.
 * dataHolder - the holder of the brochureData customizations for viewable types.
 * templateData - the holder for template customizations for viewable types.
 * def - the overlaid definitions of the FlashScript
 * actionName - the name of the script action.
 * cache - the cache of actions.  Lookup by key=vType+actionName.
 * vType - the type of the viewable demanding the FlashScript.  Eg 'pano', 'still'.
 * replacements - the parameters in name/value format.
 * inScript - Are we already in a script.  This is important because if we are then we don't have to make a script,
 * 			  We can simply return the actions string which will be included higher up.
 */
FlashMovieNode.addScript = function( actions, holder, dataHolder, templateData, def, actionName, cache, vType, replacements, inScript )
{
		//A FlashScript has actions, loop and interruptable.
		//If loop is true or interruptable is false, then we must use a FlashScript if we have 1 or more actions.
		//If loop is false and interruptable is true, then we must use a FlashScript only if we have 2 or more actions.

		//Lookup the loop and interruptable values
	var loop;
	if( typeof( def.loop ) == "undefined" )
	{
		loop = false;
	}
	else
	{
		loop = def.loop;
	}
	var interruptable;
	if( typeof( def.interruptable ) == "undefined" )
	{
		interruptable = true;
	}
	else
	{
		interruptable = def.interruptable;
	}
		//Start with null for actions.  This will contain a string of comma separated numbers
	var actionsString = null;
		//Do we have at least 2 non null actions?
	var makeScript = false;

	var cachable = true;
	for( var i in def.actions )
	{		//Loop the action definitions in the FlashScript
		var actionCall = def.actions[i];
			//This will hold the name of the action to add in
		var callActionName;
			//This will hold the parameters to pass
		var callReps;
			//This holds the viewer type (pano, still etc)
		var callVType = vType;
			//This will hold the actionID to add.
		var actionID = null;

		if( typeof( actionCall ) == "string" )
		{		//Delegate directly
			callActionName = actionCall;
			callReps = replacements;
		}
		else if( typeof( actionCall.nodeType ) == "string" )
		{		//Fully described action within
			var node = actions.addNode( actionCall.nodeType );
			cachable &= Utility.addIntoNodeN( node, [actionCall], replacements, FlashMovieNode.NODE_TYPE, true, FlashWrapper.prototype );
			actionID = node.indexInList;
		}
		else
		{		//Setup the parameters to use in addAction()
			callActionName = actionCall.actionName;
			callReps = {};
			for( var j in actionCall.params )
			{		//Loop the parameter definitions in the action definition.
					//The type we deal with separately because it is only used for caching and is passed separately.
				cachable &= Utility.doReplacements( replacements, actionCall.params[j], callReps, j );
			}
		}
		if( actionID == null )
		{
				//Run the addAction() call.  This returns null or an ID and maybe caches it separately
			var actionID = FlashMovieNode.addAction( actions, dataHolder, templateData, callActionName, cache, callReps, true );
			if( cachable )
			{		//If we think we are cachable, we have to check by looking to see if addAction() added its action to the cache
				cachable = (typeof( cache[callVType+actionName] ) != "undefined");
			}
		}
		holder.cachable &= cachable;
		if( actionID != null )
		{		//Add it to the FlashScript actions
			if( actionsString == null )
			{		//First one
				actionsString = actionID;
			}
			else
			{		//Add after a comma
				actionsString+=","+actionID;
				makeScript = true;
			}
		}
	}
	var ans;
	if( actions != null && ((!inScript && makeScript) || !interruptable || loop ) )
	{		//We must make a script under any of the above or conditions
		var script = actions.addNode("Script");
		script.loop = loop;
		script.interruptable = interruptable;
		script.actions = actionsString;
		ans = script.indexInList;
	}
	else
	{		//No need to make the script
		ans = actionsString;
	}
	holder.id = ans;
}

/**
 * publish method is called as part of the publish framework.
 * Callbacks write out the correct parameter (returning it) but also add a callback to the context.
 */
FlashWrapper.prototype.callbackPublish = function( context, id )
{
	if( this.code == null )
	{
		return;
	}
	var val = context.addCallback( this.code );
	return "<"+id+">"+val+"</"+id+">\n";
}

/**
 * We first process the named nodes into a list and then output the list
 */
FlashWrapper.prototype.delayedSortListPublish=function( context, id )
{
		//Clear the state information
	this.head = null;
	this.tail = null;
		//Loop the priority objects in order
	for( var p = this.headPriority; p != null; p = p.next )
	{		//Now we loop the nodes in the priority in any order
		for( var name in p.nodes )
		{
			p.nodes[name].notifyLink( this, "" );
		}
	}

		//Now loop all the nodes in the correct order
	var str = "";
	var count = 0;
	var addedOnlyTheFirstImage = true;
	for( var n = this.head; n != null; n = n.next )
	{
		if( n.nodeType != null )
		{			//If not deleted
			var subID = id+"["+count+"]";
			count++;
			if( n.nodeType != this.listType)
			{
				str+="<"+subID+">"+n.nodeType+"</"+subID+">\n";
			}
			if(addedOnlyTheFirstImage) {
				str+=FlashMovieNode.publishSubObj( context, subID, n.props );
				addedOnlyTheFirstImage = false;
			}
		}
	}
	//str+="<"+id+".length>"+count+"</"+id+".length>\n";
	str+="<"+id+">"+count+"</"+id+">\n";
	return str;
}

/**
 * In order to pass commands to the PanoViewer applet we re-write a controller applet each time.
 * The controller applet calls a method in either the brochure applet or the pano viewer applet in its init method.
 * This ensures that we don't have the bug whereby a DOM event thread calls into Java and while it is still in there
 * another DOM event occurs.  In this case, in most browsers on Java plugin 1.4 the java thread halts and goes into
 * an infinite loop.  Lots of things stop and the CPU is eaten at 40%.  This technique also works on Mac where
 * the java applet is not Scriptable in IE (I don't know about Safari).
 *
 * The div tag that is re-written each time has an ID of 'methodPassingLayer'.
 * It must sit in the div tag used by the main applet.  It's display style is 'none' so it is not rendered.
 *
 * The parameters that are passed are either:
 * ) mode - If 'stop' then we don't pass an extra parameter.
 * ) mode - 'thumbnail', then id is passed the viewable name.
 * ) mode - 'action', then id is passed the action name.
 */
FlashWrapper.prototype.rewriteControllerApplet = function( type, id )
{
	var divTag = Brochure.getLayer("methodPassingLayer");
	if( divTag == null )
	{
		return false;
	}
	divTag.innerHTML = "";
	var str = "";
	str+="<SCRIPT>";
	str+="function controllerApplet"+this.brochure.id+"_DoFSCommand(command, args) {\n";
  	str+=" switch (command) {\n";
	str+="  case \"printError\":\n";
	str+=" theBrochure.debug( 'document.controllerApplet"+this.brochure.id+" fscommand: printError event recieved. args:'+args);";
	str+="	alert(\"printError:\"+args);\n";
	str+="	break;\n";
	str+=" }\n";
	str+="}\n";
	str+="</SC";
	str+="RIPT>";
	str+="<OBJECT mayscript id=\"controllerApplet"+this.brochure.id+"\" name=\"controllerApplet"+this.brochure.id+"\" height=\"1\" width=\"1\" "+this.appletAttributes+" >";
	//str+="<PARAM name=\"movie\" value=\"ControllerApplet.swf?brochureAppletName=brochureApplet"+this.brochure.id+"&mode="+type+"";
	str+="<PARAM name=\"AllowScriptAccess\" value=\"always\" /\>\n";
	str+="<PARAM name=\"swliveconnect\" value=\"true\" /\>\n";
	str+="<PARAM name=\"movie\" value=\"ControllerApplet.swf?viewerName=brochureApplet&mode="+type+"";
	if( id != null )
	{
		str+="&id="+id+"";
	}
	str+="\" />";
	str+="<embed id=\"controllerApplet"+this.brochure.id+"\" name=\"controllerApplet"+this.brochure.id+"\" quality=\"high\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" height=\"1\" width=\"1\" swliveconnect=\"true\" AllowScriptAccess=\"always\" ";
	//str+="src=\"ControllerApplet.swf?brochureAppletName=brochureApplet"+this.brochure.id+"&mode="+type+"";
	str+="src=\"ControllerApplet.swf\" FlashVars = \"viewerName=brochureApplet&mode="+type+"";
	if( id != null )
	{
		str+="&id="+id+"";
	}
	str+="\"";
	str+="></embed>\n";
	str+="</OBJECT>";
	this.brochure.debug( "ControllerApplet.swf="+str);
	divTag.innerHTML = str;
	return true;
}

FlashWrapper.prototype.useIFrame = function()
{
	return (window.navigator.platform == "MacPPC" && window.navigator.userAgent.indexOf("Safari") != -1);
}


FlashWrapper.prototype.getMuteState = function()
{
	var ans = this.isMute ? "mute" : (this.soundStarted ? "active" : "nosound");
	this.brochure.debug("getMuteState()="+ans);
	return ans;
}

FlashWrapper.prototype.setFirstSoundPlaying = function( mute )
{
	this.soundStarted = true;
	this.setMute(mute);
}

FlashWrapper.prototype.setMute=function( mute )
{
	if( typeof( mute ) == "boolean" )
	{
		this.isMute = mute;
	}
	else if( "true" == mute )
	{
		this.isMute = true;
	}
	else
	{
		this.isMute = false;
	}
	if( this.muteStateChangeListener && typeof(this.muteStateChangeListener.onMuteStateChanged) == "function" )
	{
		this.muteStateChangeListener.onMuteStateChanged();
	}
}

FlashWrapper.prototype.registerMuteStateListener=function(obj)
{
	this.muteStateChangeListener = obj;
}

