/**
 * AppletWrapper.js
 *
 * This is the implementation of the Java 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 Applet or Flash object and the rest of brochure
 * system pass through this object.
 *
 * This version is the implementation of the Java version.  See FlashWrapper for the Flash version.
 *
 * Javascript pseudo-classes defined are:
 *  AppletWrapper - the viewer top level object
 *  JavaApplet - an object used in publishing to represent the overall applet to publish
 *  JavaList - an object used in publishing to represent a list of rooms, layers, actions etc
 *  JavaAppletNode - an object used in publishing to represent a node in a list
 *  JavaActionsScript - 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 JavaApplet 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 PARAM tags.
 * The JavaApplet has a publish() method to this effect.
 * The JavaList represents a list of sub-objects called Nodes.
 * For example, the list of Rooms, the list of Layers, the list of Actions and a JavaList of Effects.
 * The JavaAppletNode is always just under a JavaList.
 * The JavaActionsScript 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 JavaAppletNode namespace holds a number of utility methods:
 * publishSubObj() - Part of the publishing framework to publish any object network even if not constructed with
 * 					JavaAppletNode, JavaList 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.
 */

AppletWrapper.prototype.constructor = AppletWrapper;

/**
 * Constructor.
 * Is given a reference to the brochure object.
 */
function AppletWrapper(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.rooms = null;
		//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
 */
AppletWrapper.prototype.getInitialExternalContent = function()
{
		//Similar to what will be done for the applet each time
	var applet = new JavaApplet();
	var template = this.brochure.getTemplate();
	var templateData = template.getViewerData('Java');
	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:1px;height:1px;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=\"0\" height=\"0\"></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:3;\"></DIV>\n";
	str+="<DIV id=\"brochureAppletLayer\" style=\"position:absolute;top:0px;left:0px;width:1px;height:1px;overflow:hidden;z-index:2;\"></DIV>\n";
	str+="</DIV>";
	return str;
}

/**
 * Called initially as part of page writing to put some initial content into the viewer DIV tag.
 */
AppletWrapper.prototype.getViewerLayerContent = function()
{
	var str = "";
	str+="<DIV id=\"innerAppletLayer\" style=\"position:absolute;z-index:1;width:100%;height:100%;\">";
	if( this.isWriteMainAppletOnce() )
	{
		str+="<APPLET codeBase=\"" + cBase + "\"  mayscript name=\"applet"+this.brochure.id+"\" code=\"PanoViewer.class\" width=\"100%\" height=\"100%\" "+this.appletAttributes+" >\n";
		str+="<PARAM name=\"id\" value=\"applet"+this.brochure.id+"\" />";
		str+="<PARAM name=\"usePluggableParameters\" value=\"true\" />";
		str+="</APPLET>";
	}
	str+="</DIV>";
	this.brochure.debug("getViewerLayerContent:\n"+str);
	return str;
}

/**
 * Called on loading a page for example to re-do the viewer layer
 */
AppletWrapper.prototype.formatViewerLayer = function( viewerLayerDivTag, flag, params )
{
	var writeOnce = this.isWriteMainAppletOnce();
	this.brochure.debug( "writeOnce="+writeOnce );
		//Next we clear out and re-write the brochure applet
	var divTag = Brochure.getLayer("brochureAppletLayer");
	if( divTag != null )
	{
		//var hasReqestedVersion = DetectJavaVer("Sun",1, 5, 0);
		//if (hasReqestedVersion) { //Compatible with all versions of java, so this check isn't necessary
			var str = "";
			str+="<APPLET codeBase=\"" + cBase + "\"  mayscript name=\"brochureApplet"+this.brochure.id+"\" height=\"1\" code=\"BrochureApplet.class\" "+this.appletAttributes+" >\n";
			str+="<PARAM name=\"id\" value=\"brochureApplet"+this.brochure.id+"\" />";
			str+="<PARAM name=\"brochure.panoAppletName\" value=\"applet"+this.brochure.id+"\" />\n";
			/*TODO DEBUG str+="<PARAM name=\"debug\" value=\"true\" />\n";*/
			str+=this.controllerParamTags;
			if( writeOnce )
			{
				str+=this.paramTags;		//Put these here and delegate them through
			}
			str+="</APPLET>\n";
			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);
				}
				str+="<APPLET codeBase=\"" + cBase + "\"  mayscript name=\"applet"+this.brochure.id+"\" code=\"PanoViewer.class\" width=\""+width+"\" height=\""+height+"\" "+this.appletAttributes+" >\n";
				str+="<PARAM name=\"id\" value=\"applet"+this.brochure.id+"\" />";
				str+=this.paramTags;
				str+="</APPLET>";
				this.brochure.debug( "main applet='"+str+"'" );
				divTag.innerHTML = str;
			//}
		}
	}
	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;
	}
}

AppletWrapper.prototype.isWriteMainAppletOnce = function()
{
	return this.brochure.isIE();
}

/**
 * Called by the termination code to clean up all the remaining stuff from the initial load.
 */
AppletWrapper.prototype.unLoad = function()
{
	var divTag = Brochure.getLayer("viewerLayer");
	divTag.innerHTML = null;
}

/**
 * This is called by the unloadPage mechanism instead of clearing the entire viewerLayer
 */
AppletWrapper.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).
 */
AppletWrapper.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).
 */
AppletWrapper.prototype.loadPage = function( pageName, page )
{
		//Clear out the room and layer information
	this.rooms = {};
		//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('Java');
		//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 JavaApplet();
		//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.actions = new Array();
			//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;
		if( typeof( dataV.roomType ) == "string" )
		{
			roomType = dataV.roomType;
		}
		else
		{
			roomType = templateDataV.defaultRoomType;
		}
			//This is the set of tags being prepared
		var v = myV.v = applet.rooms.addNode(roomType);
		this.rooms[v.indexInList] = id;

			//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.actions.hide = JavaAppletNode.addAction( applet.actions, null, templateData, "hide", actionCache, reps, false );
		myV.actions.show = JavaAppletNode.addAction( applet.actions, null, templateData, "show", actionCache, reps, false );

			//Add the showOff action to the thumbnails
		myV.thumbnails[id] =
			JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "showOff", actionCache, reps, false );
			//Add the standard actions in - left, right, zoom etc
		for( id in standardActionNames )
		{
			var actionName = standardActionNames[id];
			if( actionName != "stop" && actionName != "guidedTour" )
			{
				myV.actions[actionName] = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, actionName, actionCache, reps, false );
			}
		}
	}

			//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
	reps = {};
	for( var id in page.viewables )
	{
			//Fill other global replacement parameters
		reps.page = page;
		reps.viewable = this;
			//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;
				//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 hotspots = myV.v.hotspots = new JavaList(defaultHotspotType);
				//This maps the viewable name to the hotspot id
			myV.hotspots = {};
				//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 hotspot = hotspots.addNode(hotspotType);
					//Fill in replacements common to both types of hotspot
				reps.hs = hotspot;
				reps.dataHS = dataHotspot;

				if( typeof(dataHotspot.targetViewableName) == "string" )
				{		//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.hotspots[toID]=hotspot.indexInList;
						//Get the data from the brochure for the "to" viewable
					var dataToV = page.viewables[toID];
					if (typeof(dataHotspot.text) == "undefined") {
						hotspot.text = dataToV.short_desc;
					}

						//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["extra"] )
						{		//The brochure customizes the overall hotspot definition
							step4 = brochureCustomizations[fromType].allHotspots["extra"];
						}
					}
					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 );
					}
					var ss = "";
					for (var ii in reps) {
						ss+=ii+"="+reps[ii]+"\n";
					}
					Utility.addIntoNodeN( hotspot, datas, reps, hsTemplateData.excludeProps, true, this );
					hotspot.action=
						JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "hotspotNormal", actionCache, reps, false );

					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;

						//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 )
						{		//The brochure customizes the overall hotspot definition
							step4 = brochureCustomizations[fromType].allHotspots.extra;
						}
					}
					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") {
						hotspot.text = defText;
					}
					Utility.addIntoNodeN( hotspot, datas, reps, hsTemplateData.excludeProps, true, this );
					if( typeof(dataHotspot.url) == "string" )
					{		//Link to an external URL
						hotspot.action=
							JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "hotspotToURL", actionCache, reps, false );
						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.hotspots && typeof( myV.hotspots[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.hotspots[myV.hotspots[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] = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "thumbnailWithHotspot", actionCache, reps, false );

						//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] = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "thumbnail", actionCache, reps, false );
					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 = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "showOffTour", actionCache, reps, true );
			}
			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 = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "tourTransitionWithHotspot", actionCache, reps, true );
					delete reps.hs;
				}
				else
				{
					trans = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "tourTransition", actionCache, reps, true );
				}
					//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 = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "tourInit", actionCache, reps, true );
			}
			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 = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "tourStartWithHotspot", actionCache, reps, true );
					delete reps.hs;
				}
				else
				{
					prefix = JavaAppletNode.addAction( applet.actions, brochureCustomizations, templateData, "tourStart", actionCache, reps, true );
				}
				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].actions.guidedTour = guidedTourAction;
			}
			else
			{
				var node = applet.actions.addNode("Script");
				node.loop=false;
				node.interruptable=true;
				node.actions=guidedTourAction;
				this.viewables[id].actions.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.rooms[applet.startroom].initAction = myV.thumbnails[page.currentViewableName];

		//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 != "actions" && j != "thumbnails" )
			{
				delete this.viewables[id][j];
			}
		}
	}
		//Build the controller tags
	var str = "";
	var max = -1;
	for( var i in this.rooms )
	{
		if( max < (1*i) )
		{
			max = (1*i);
		}
		str+="<PARAM name=\"brochure.rooms["+i+"]\" value=\""+this.rooms[i]+"\" />\n";
	}
	str+="<PARAM name=\"brochure.rooms.length\" value=\""+(1+1*max)+"\" />\n";
	for( var id in this.viewables )
	{
		var obj = this.viewables[id];
		for( var j in obj.actions )
		{
			var val = obj.actions[j];
			if( val != null )
			{
				str+="<PARAM name=\"brochure.actions["+id+"]["+j+"]\" value=\""+val+"\" />\n";
			}
		}
		for( var j in obj.thumbnails )
		{
			var val = obj.thumbnails[j];
			if( val != null )
			{
				str+="<PARAM name=\"brochure.thumbnails["+id+"]["+j+"]\" value=\""+val+"\" />\n";
			}
		}
	}
	this.controllerParamTags = str;
	if( this.useIFrame )
	{		//Note that callbackURL should be stored in the template definition
		this.paramTags = "<PARAM name=\"callbackFrame\" value=\"messagePassingFrame\" />";
	}
	else
	{
		this.paramTags = "";
	}
	this.paramTags += applet.publish(this);
	this.currentViewable = page.currentViewableName;
}


/**
 * Callback from applet.onLoad so we know the callbacks are working
 */
AppletWrapper.prototype.setCallbacksWorking = function()
{
	this.brochure.debug("Callbacks are working");
	this.callbacksWorking = true;
	this.brochure.notifyViewableChanged(this.currentViewable);
}

/**
 * Instruct the applet to show the specified viewable.
 * If it succeeded then return true, else false.
 */
AppletWrapper.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;
}

AppletWrapper.prototype.notifyViewableChanged = function( id )
{
	this.currentViewable = id;
}

/**
 * Tells the applet to do some action.
 * id is a named action.
 */
AppletWrapper.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.
 */
AppletWrapper.prototype.getButtonState = function( id )
{
	var actions = this.viewables[this.currentViewable].actions;
	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!!!!
 */
AppletWrapper.prototype.getCurrentViewable = function(search)
{
	if( this.callbacksWorking || !search )
	{
		return this.currentViewable;
	}
	var ans = document.applets.applet.jsGetActiveRoomID();
	return this.rooms[ans];
}

/**
 * Callback from Callback.publish( context, id ).
 */
AppletWrapper.prototype.addCallback = function( code )
{
	var callbackNum = this.numCallbacks++;
	var val = "callback"+callbackNum;
	this.callbacks[val]=code;
	return val;
}

/**
 * JavaApplet.
 * The applet is the root of the publishing network for applet param tags.
 */
function JavaApplet()
{
	this.rooms = new JavaList("SpheriCylinderRoom");
	this.layers = new JavaList("");
	this.actions = new JavaList("");
}

/**
 * Publishing the applet returns the string of parameter tags for this applet.
 */
JavaApplet.prototype.publish = function(context)
{
	var str="";
	for( var i in this )
	{
		var obj = this[i];
		if( typeof( JavaApplet.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+=JavaAppletNode.publishSubObj( context,i, obj );
				}
			}
			else
			{
				str+="<PARAM name=\""+i+"\" value=\""+obj+"\" />\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.
 */
JavaApplet.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 "";
}

/**
 * JavaList
 */
function JavaList(listType)
{
	this.nNodes=0;
	this.listType = listType;
}

/**
 * Adds a node into a list of the given type.
 * This builds an JavaAppletNode object that has its own publish method.
 * The type is written out as a parameter at the level of the stub.
 */
JavaList.prototype.addNode=function(nodeType)
{
	return this[this.nNodes]=new JavaAppletNode(nodeType,this.nNodes++);
}

/**
 * Publishing a list involves writing its length too.
 */
JavaList.prototype.publish=function( context, id )
{
	var str = "";
	var n = this.nNodes;
	str+="<PARAM name=\""+id+".length\" value=\""+n+"\" />\n";
	for( var i = 0; i < n; i++ )
	{
		var node = this[i];
		var subID = id+"["+i+"]";
		if( typeof( node.nodeType ) == "string" && node.nodeType != this.listType)
		{
			str+="<PARAM name=\""+subID+"\" value=\""+node.nodeType+"\" />\n";
		}
		str+=node.publish(context,subID);
	}
	return str;
}

/**
 * ----
 * JavaAppletNode
 * ----
 * A JavaAppletNode is an object in a JavaList within the JavaApplet.
 * It has a nodeType and an indexInList.
 * These two properties are handled by the JavaList.
 *
 * It has one method - publish().  This is called as part of the publishing framework.
 */
function JavaAppletNode( nodeType, indexInList )
{
	this.nodeType = nodeType;
	this.indexInList = indexInList;
}

/**
 * This is called by the JavaList object as part of publishing
 */
JavaAppletNode.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 );
					}
					else
					{
						str+=JavaAppletNode.publishSubObj( context, id+"."+i, obj );
					}
				}
			}
			else if( i != 'publish' )
			{
				str+="<PARAM name=\""+id+"."+i+"\" value=\""+obj+"\" />\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 JavaAppletNode and not a JavaList).
 */
JavaAppletNode.publishSubObj = function( context, id, obj )
{
	if( typeof( obj.nodeType ) == "string" && obj.nodeType == "Resource" )
	{
		return JavaAppletNode.publishResource( context, id, obj );
	}
	else
	{
		var str = "";
		for( var i in obj )
		{
			var val = obj[i];
			if( i == "nodeType" )
			{
				str+="<PARAM name=\""+id+"\" value=\""+val+"\" />\n";
			}
			else if( typeof( val ) == "object" )
			{
				if (val == null) {
					str+="<PARAM name\""+id+"."+i+"\" value=\"null\" />\n";
				} else if( typeof( val["publish"] ) == "function" )
				{
					str+=val.publish( context,id+"."+i );
				}
				else
				{
					str+=JavaAppletNode.publishSubObj( context,id+"."+i, val );
				}
			}
			else
			{
				str+="<PARAM name=\""+id+"."+i+"\" value=\""+val+"\" />\n";
			}
		}
		return str;
	}
}

/**
 * Resource nodes are not even a separate js type, they have a hook here to re-write their url.
 */
JavaAppletNode.publishResource = function( context, id, obj )
{
	var str = "";
	for( var i in obj )
	{
		switch( i )
		{
		case "nodeType":break;
		case "url":
			str+="<PARAM name=\""+id+"\" value=\""+obj.url+"\" />\n";
			break;
		default:
			var val = obj[i];
			if( typeof( val ) == "object" )
			{
				if( typeof( val["publish"] ) == "function" )
				{
					str+=val.publish( context,id+"."+i );
				}
				else
				{
					str+=JavaAppletNode.publishSubObj( context,id+"."+i, val );
				}
			}
			else
			{
				str+="<PARAM name=\""+id+"."+i+"\" value=\""+val+"\" />\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?
 */
JavaAppletNode.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;
	}
	JavaAppletNode.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
 */
JavaAppletNode.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 JavaActionsScript and if so addScript() is called.
 * Otherwise a new JavaAppletNode is created and addIntoNodeN() is called passing the stack of object networks to merge in.
 */
JavaAppletNode.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].actions ) == "object" )
	{
		data = dataHolder[vType].actions;
	}
	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;
				JavaAppletNode.addActionInternal( actions, holder, datas, dataHolder, templateData, dataActionName, cache, replacements, inScript );
				holder.cachable = false;
			}
			else
			{
				JavaAppletNode.addActionInternal( actions, holder, datas, dataHolder, templateData, dataActionName, cache, replacements, inScript );
				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" )
				{
					JavaAppletNode.addScript( actions, holder, dataHolder, templateData, data[actionName], actionName, cache, vType, replacements, inScript );
					if( holder.cachable )
					{
						cache[cacheKey] = holder.id;
					}
				}
				else
				{
					datas.push( data[actionName] );
					var action = actions.addNode(data[actionName].nodeType);
					if( Utility.addIntoNodeN( action, datas, replacements, JavaAppletNode.NODE_TYPE, true, AppletWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = action.indexInList;
					}
					else
					{
						holder.id = action.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;
				JavaAppletNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				holder.cachable = false;
			}
			else
			{
				JavaAppletNode.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" )
				{
					JavaAppletNode.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 action = actions.addNode(templateActionDef.nodeType);
					if( Utility.addIntoNodeN( action, datas, replacements, JavaAppletNode.NODE_TYPE, true, AppletWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = action.indexInList;
					}
					else
					{
						holder.id = action.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;
				JavaAppletNode.addActionInternal( actions, holder, datas, dataHolder, templateData, templateActionDef, cache, replacements, inScript );
				holder.cachable = false;
			}
			else
			{
				JavaAppletNode.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
					JavaAppletNode.addScript( actions, holder, dataHolder, templateData, templateActionDef, actionName, cache, vType, replacements, inScript );
					if( holder.cachable )
					{
						cache[cacheKey] = holder.id;
					}
				}
				else
				{
					datas.push( templateActionDef );
					var action = actions.addNode(templateActionDef.nodeType);
					if( Utility.addIntoNodeN( action, datas, replacements, JavaAppletNode.NODE_TYPE, true, AppletWrapper.prototype ) )
					{		//Cachable
						cache[cacheKey] = holder.id = action.indexInList;
					}
					else
					{
						holder.id = action.indexInList;
						holder.cachable = false;
					}
				}
				if( cacheKey2 != null )
				{
					cache[cacheKey2] = holder.id;
				}
				return;
			}
			datas.push( templateActionDef );
			break;
		default:		//Should be undefined - legal
			break;
		}
	}
}

/**
 * Adding a JavaActionsScript 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 JavaActionsScript
 * 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 JavaActionsScript.  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.
 */
JavaAppletNode.addScript = function( actions, holder, dataHolder, templateData, def, actionName, cache, vType, replacements, inScript )
{
		//A JavaActionsScript has actions, loop and interruptable.
		//If loop is true or interruptable is false, then we must use a JavaActionsScript if we have 1 or more actions.
		//If loop is false and interruptable is true, then we must use a JavaActionsScript 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 JavaActionsScript
		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, JavaAppletNode.NODE_TYPE, true, AppletWrapper.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 = JavaAppletNode.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 JavaActionsScript 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.
 */
AppletWrapper.prototype.callbackPublish = function( context, id )
{
	if( this.code == null )
	{
		return;
	}
	var val = context.addCallback( this.code );
	return "<PARAM name=\""+id+"\" value=\""+val+"\" />\n";
}

/**
 * We first process the named nodes into a list and then output the list
 */
AppletWrapper.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;
	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+="<PARAM name=\""+subID+"\" value=\""+n.nodeType+"\" />\n";
			}
			str+=JavaAppletNode.publishSubObj( context, subID, n.props );
		}
	}
	str+="<PARAM name=\""+id+".length\" value=\""+count+"\" />\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.
 */
AppletWrapper.prototype.rewriteControllerApplet = function( type, id )
{
	var divTag = Brochure.getLayer("methodPassingLayer");
	if( divTag == null )
	{
		return false;
	}
	divTag.innerHTML = "";
	var str = "<APPLET codeBase=\"" + cBase + "\"  mayscript name=\"controllerApplet"+this.brochure.id+"\" height=\"1\" width=\"1\" code=\"ControllerApplet.class\" "+this.appletAttributes+" >";
	str+="<PARAM name=\"brochureAppletName\" value=\"brochureApplet"+this.brochure.id+"\" />\n";
	str+="<PARAM name=\"mode\" value=\""+type+"\" />";
	if( id != null )
	{
		str+="<PARAM name=\"id\" value=\""+id+"\" />";
	}
	str+="</APPLET>";
	divTag.innerHTML = str;
	return true;
}

AppletWrapper.prototype.useIFrame = function()
{
	return (window.navigator.platform == "MacPPC" && window.navigator.userAgent.indexOf("Safari") != -1);
}


AppletWrapper.prototype.getMuteState = function()
{
	var ans = this.isMute ? "mute" : (this.soundStarted ? "active" : "nosound");
	this.brochure.debug("getMuteState()="+ans);
	return ans;
}

AppletWrapper.prototype.setFirstSoundPlaying = function( mute )
{
	this.soundStarted = true;
	this.setMute(mute);
}

AppletWrapper.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();
	}
}

AppletWrapper.prototype.registerMuteStateListener=function(obj)
{
	this.muteStateChangeListener = obj;
}

