/**

 * 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 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 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 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 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;

}


