/* 
 * 
 * H-care - HDA Web Client - 3.1.3 - Build: 200903171617
 * 
 *  
 * Here will be placed a disclaimer and a copyright info
 * 
 * 
 * 
 */
/**
 * Create a new Exception object. name: The name of the exception. message: The
 * exception message.
 */
function Exception(name, message) {
	if (name)
		this.name = name;
	if (message)
		this.message = message;
};

/**
 * Set the name of the exception.
 */
Exception.prototype.setName = function(name) {
	this.name = name;
};

/**
 * Get the exception's name.
 */
Exception.prototype.getName = function() {
	return this.name;
};

/**
 * Set a message on the exception.
 */
Exception.prototype.setMessage = function(msg) {
	this.message = msg;
};

/**
 * Get the exception message.
 */
Exception.prototype.getMessage = function() {
	return this.message;
};

/**
 * Generates a browser-specific Flash tag. Create a new instance, set whatever
 * properties you need, then call either toString() to get the tag as a string,
 * or call write() to write the tag out.
 */

/**
 * Creates a new instance of the FlashTag. src: The path to the SWF file. width:
 * The width of your Flash content. height: the height of your Flash content.
 */
function FlashTag(src, width, height, bgcolor) {
	this.src = src;
	this.width = width;
	this.height = height;
	this.version = '7,0,14,0';
	this.id = null;
	this.bgcolor = bgcolor;
	this.flashVars = null;
};

/**
 * Sets the Flash version used in the Flash tag.
 */
FlashTag.prototype.setVersion = function(v) {
	this.version = v;
};

/**
 * Sets the ID used in the Flash tag.
 */
FlashTag.prototype.setId = function(id) {
	this.id = id;
};

/**
 * Sets the background color used in the Flash tag.
 */
FlashTag.prototype.setBgcolor = function(bgc) {
	this.bgcolor = bgc;
};

/**
 * Sets any variables to be passed into the Flash content.
 */
FlashTag.prototype.setFlashvars = function(fv) {
	this.flashVars = fv;
};

/**
 * Get the Flash tag as a string.
 */
FlashTag.prototype.toString = function() {
	var ie = (navigator.appName.indexOf("Microsoft") != -1) ? 1 : 0;
	var flashTag = new String();
	flashTag += '<object standby="Loading..." type="application/x-shockwave-flash" class="x-media x-media-swf" style="display: inline;" ';
	if (this.id != null) {
		flashTag += 'id="' + this.id + '" ';
		flashTag += 'name="' + this.id + '" ';
	}
	flashTag += 'width="' + this.width + '" ';
	flashTag += 'height="' + this.height + '" ';
	flashTag += 'data="' + this.src + '" ';
	flashTag += '>';

	flashTag += '<param name="movie" value="' + this.src + '"/>';
	flashTag += '<param name="quality" value="high"/>';
	flashTag += '<param name="bgcolor" value="#' + this.bgcolor + '"/>';
	if (this.flashVars != null) {
		flashTag += '<param name="flashvars" value="' + this.flashVars + '"/>';
	}
	flashTag += '<div>The Adobe Flash Player is required.<br/><a target="_flash" href="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif"/></a></div>';
	flashTag += '</object>';

	return flashTag;
};

/**
 * Write the Flash tag out. Pass in a reference to the document to write to.
 */
FlashTag.prototype.write = function(doc) {
	doc.write(this.toString());
};

/**
 * The FlashSerializer serializes JavaScript variables of types object, array,
 * string, number, date, boolean, null or undefined into XML.
 */

/**
 * Create a new instance of the FlashSerializer. useCdata: Whether strings
 * should be treated as character data. If false, strings are simply XML
 * encoded.
 */
function FlashSerializer(useCdata) {
	this.useCdata = useCdata;
};

/**
 * Serialize an array into a format that can be deserialized in Flash. Supported
 * data types are object, array, string, number, date, boolean, null, and
 * undefined. Returns a string of serialized data.
 */
FlashSerializer.prototype.serialize = function(args) {
	var qs = new String();

	for (var i = 0; i < args.length; ++i) {
		switch (typeof(args[i])) {
			case 'undefined' :
				qs += 't' + (i) + '=undf';
				break;
			case 'string' :
				qs += 't' + (i) + '=str&d' + (i) + '=' + escape(args[i]);
				break;
			case 'number' :
				qs += 't' + (i) + '=num&d' + (i) + '=' + escape(args[i]);
				break;
			case 'boolean' :
				qs += 't' + (i) + '=bool&d' + (i) + '=' + escape(args[i]);
				break;
			case 'object' :
				if (args[i] == null) {
					qs += 't' + (i) + '=null';
				} else if (args[i] instanceof Date) {
					qs += 't' + (i) + '=date&d' + (i) + '='
							+ escape(args[i].getTime());
				} else // array or object
				{
					try {
						qs += 't' + (i) + '=xser&d' + (i) + '='
								+ escape(this._serializeXML(args[i]));
					} catch (exception) {
						throw new Exception("FlashSerializationException",
								"The following error occurred during complex object serialization: "
										+ exception.getMessage());
					}
				}
				break;
			default :
				throw new Exception(
						"FlashSerializationException",
						"You can only serialize strings, numbers, booleans, dates, objects, arrays, nulls, and undefined.");
		}

		if (i != (args.length - 1)) {
			qs += '&';
		}
	}

	return qs;
};

/**
 * Private
 */
FlashSerializer.prototype._serializeXML = function(obj) {
	var doc = new Object();
	doc.xml = '<fp>';
	this._serializeNode(obj, doc, null);
	doc.xml += '</fp>';
	return doc.xml;
};

/**
 * Private
 */
FlashSerializer.prototype._serializeNode = function(obj, doc, name) {
	switch (typeof(obj)) {
		case 'undefined' :
			doc.xml += '<undf' + this._addName(name) + '/>';
			break;
		case 'string' :
			doc.xml += '<str' + this._addName(name) + '>'
					+ this._escapeXml(obj) + '</str>';
			break;
		case 'number' :
			doc.xml += '<num' + this._addName(name) + '>' + obj + '</num>';
			break;
		case 'boolean' :
			doc.xml += '<bool' + this._addName(name) + ' val="' + obj + '"/>';
			break;
		case 'object' :
			if (obj == null) {
				doc.xml += '<null' + this._addName(name) + '/>';
			} else if (obj instanceof Date) {
				doc.xml += '<date' + this._addName(name) + '>' + obj.getTime()
						+ '</date>';
			} else if (obj instanceof Array) {
				doc.xml += '<array' + this._addName(name) + '>';
				for (var i = 0; i < obj.length; ++i) {
					this._serializeNode(obj[i], doc, null);
				}
				doc.xml += '</array>';
			} else {
				doc.xml += '<obj' + this._addName(name) + '>';
				for (var n in obj) {
					if (typeof(obj[n]) == 'function')
						continue;
					this._serializeNode(obj[n], doc, n);
				}
				doc.xml += '</obj>';
			}
			break;
		default :
			throw new Exception(
					"FlashSerializationException",
					"You can only serialize strings, numbers, booleans, objects, dates, arrays, nulls and undefined");
			break;
	}
};

/**
 * Private
 */
FlashSerializer.prototype._addName = function(name) {
	if (name != null) {
		return ' name="' + name + '"';
	}
	return '';
};

/**
 * Private
 */
FlashSerializer.prototype._escapeXml = function(str) {
	if (this.useCdata)
		return '<![CDATA[' + str + ']]>';
	else
		return str.replace(/&/g, '&amp;').replace(/</g, '&lt;');
};

/**
 * The FlashProxy object is what proxies function calls between JavaScript and
 * Flash. It handles all argument serialization issues.
 */

/**
 * Instantiates a new FlashProxy object. Pass in a uniqueID and the name
 * (including the path) of the Flash proxy SWF. The ID is the same ID that needs
 * to be passed into your Flash content as lcId.
 */
function FlashProxy(uid, proxySwfName) {
	this.uid = uid;
	this.proxySwfName = proxySwfName;
	this.flashSerializer = new FlashSerializer(false);
};

/**
 * Call a function in your Flash content. Arguments should be: 1. ActionScript
 * function name to call, 2. any number of additional arguments of type object,
 * array, string, number, boolean, date, null, or undefined.
 */
FlashProxy.prototype.call = function() {

	if (arguments.length == 0) {
		throw new Exception(
				"Flash Proxy Exception",
				"The first argument should be the function name followed by any number of additional arguments.");
	}

	var qs = 'lcId=' + escape(this.uid) + '&functionName='
			+ escape(arguments[0]);

	if (arguments.length > 1) {
		var justArgs = new Array();
		for (var i = 1; i < arguments.length; ++i) {
			justArgs.push(arguments[i]);
		}
		qs += ('&' + this.flashSerializer.serialize(justArgs));
	}

	var divName = '_flash_proxy_' + this.uid;
	if (!document.getElementById(divName)) {
		var newTarget = document.createElement("div");
		newTarget.id = divName;
		document.body.appendChild(newTarget);
	}
	var target = document.getElementById(divName);
	var ft = new FlashTag(this.proxySwfName, 1, 1);
	ft.setVersion('6,0,65,0');
	ft.setFlashvars(qs);
	// alert(ft.toString());
	target.innerHTML = ft.toString();
};

/**
 * This is the function that proxies function calls from Flash to JavaScript. It
 * is called implicitly.
 */
FlashProxy.callJS = function() {
	// alert("FlashProxy.callJS "+arguments);
	var functionToCall = eval(arguments[0]);
	var argArray = new Array();
	for (var i = 1; i < arguments.length; ++i) {
		argArray.push(arguments[i]);
	}
	functionToCall.apply(functionToCall, argArray);
};
/*
 * Copyright 2005 Joe Walker
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Declare an object to which we can add real functions.
 */
if (dwr == null) var dwr = {};
if (dwr.engine == null) dwr.engine = {};
if (DWREngine == null) var DWREngine = dwr.engine;

/**
 * Set an alternative error handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setErrorHandler = function(handler) {
  dwr.engine._errorHandler = handler;
};

/**
 * Set an alternative warning handler from the default alert box.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setWarningHandler = function(handler) {
  dwr.engine._warningHandler = handler;
};

/**
 * Setter for the text/html handler - what happens if a DWR request gets an HTML
 * reply rather than the expected Javascript. Often due to login timeout
 */
dwr.engine.setTextHtmlHandler = function(handler) {
  dwr.engine._textHtmlHandler = handler;
};

/**
 * Set a default timeout value for all calls. 0 (the default) turns timeouts off.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.setTimeout = function(timeout) {
  dwr.engine._timeout = timeout;
};

/**
 * The Pre-Hook is called before any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPreHook = function(handler) {
  dwr.engine._preHook = handler;
};

/**
 * The Post-Hook is called after any DWR remoting is done.
 * @see getahead.org/dwr/browser/engine/hooks
 */
dwr.engine.setPostHook = function(handler) {
  dwr.engine._postHook = handler;
};

/**
 * Custom headers for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setHeaders = function(headers) {
  dwr.engine._headers = headers;
};

/**
 * Custom parameters for all DWR calls
 * @see getahead.org/dwr/????
 */
dwr.engine.setParameters = function(parameters) {
  dwr.engine._parameters = parameters;
};

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.XMLHttpRequest = 1;

/** XHR remoting type constant. See dwr.engine.set[Rpc|Poll]Type() */
dwr.engine.IFrame = 2;

/** XHR remoting type constant. See dwr.engine.setRpcType() */
dwr.engine.ScriptTag = 3;

/**
 * Set the preferred remoting type.
 * @param newType One of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setRpcType = function(newType) {
  if (newType != dwr.engine.XMLHttpRequest && newType != dwr.engine.IFrame && newType != dwr.engine.ScriptTag) {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidRpcType", message:"RpcType must be one of dwr.engine.XMLHttpRequest or dwr.engine.IFrame or dwr.engine.ScriptTag" });
    return;
  }
  dwr.engine._rpcType = newType;
};

/**
 * Which HTTP method do we use to send results? Must be one of "GET" or "POST".
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setHttpMethod = function(httpMethod) {
  if (httpMethod != "GET" && httpMethod != "POST") {
    dwr.engine._handleError(null, { name:"dwr.engine.invalidHttpMethod", message:"Remoting method must be one of GET or POST" });
    return;
  }
  dwr.engine._httpMethod = httpMethod;
};

/**
 * Ensure that remote calls happen in the order in which they were sent? (Default: false)
 * @see getahead.org/dwr/browser/engine/ordering
 */
dwr.engine.setOrdered = function(ordered) {
  dwr.engine._ordered = ordered;
};

/**
 * Do we ask the XHR object to be asynchronous? (Default: true)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setAsync = function(async) {
  dwr.engine._async = async;
};

/**
 * Does DWR poll the server for updates? (Default: false)
 * @see getahead.org/dwr/browser/engine/options
 */
dwr.engine.setActiveReverseAjax = function(activeReverseAjax) {
  if (activeReverseAjax) {
    // Bail if we are already started
    if (dwr.engine._activeReverseAjax) return;
    dwr.engine._activeReverseAjax = true;
    dwr.engine._poll();
  }
  else {
    // Can we cancel an existing request?
    if (dwr.engine._activeReverseAjax && dwr.engine._pollReq) dwr.engine._pollReq.abort();
    dwr.engine._activeReverseAjax = false;
  }
  // TODO: in iframe mode, if we start, stop, start then the second start may
  // well kick off a second iframe while the first is still about to return
  // we should cope with this but we don't
};

/**
 * The default message handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultErrorHandler = function(message, ex) {
  dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true);
  if (message == null || message == "") alert("A server error has occured.");
  // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
  else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message);
  else alert(message);
};

/**
 * The default warning handler.
 * @see getahead.org/dwr/browser/engine/errors
 */
dwr.engine.defaultWarningHandler = function(message, ex) {
  dwr.engine._debug(message);
};

/**
 * For reduced latency you can group several remote calls together using a batch.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.beginBatch = function() {
  if (dwr.engine._batch) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchBegun", message:"Batch already begun" });
    return;
  }
  dwr.engine._batch = dwr.engine._createBatch();
};

/**
 * Finished grouping a set of remote calls together. Go and execute them all.
 * @see getahead.org/dwr/browser/engine/batch
 */
dwr.engine.endBatch = function(options) {
  var batch = dwr.engine._batch;
  if (batch == null) {
    dwr.engine._handleError(null, { name:"dwr.engine.batchNotBegun", message:"No batch in progress" });
    return;
  }
  dwr.engine._batch = null;
  if (batch.map.callCount == 0) return;

  // The hooks need to be merged carefully to preserve ordering
  if (options) dwr.engine._mergeBatch(batch, options);

  // In ordered mode, we don't send unless the list of sent items is empty
  if (dwr.engine._ordered && dwr.engine._batchesLength != 0) {
    dwr.engine._batchQueue[dwr.engine._batchQueue.length] = batch;
  }
  else {
    dwr.engine._sendData(batch);
  }
};

/** @deprecated */
dwr.engine.setPollMethod = function(type) { dwr.engine.setPollType(type); };
dwr.engine.setMethod = function(type) { dwr.engine.setRpcType(type); };
dwr.engine.setVerb = function(verb) { dwr.engine.setHttpMethod(verb); };
dwr.engine.setPollType = function() { dwr.engine._debug("Manually setting the Poll Type is not supported"); };

//==============================================================================
// Only private stuff below here
//==============================================================================

/** The original page id sent from the server */
dwr.engine._origScriptSessionId = "0A8E87B857FF91D3D8D5373380B69BFB";

/** The session cookie name */
dwr.engine._sessionCookieName = "JSESSIONID"; // JSESSIONID

/** Is GET enabled for the benefit of Safari? */
dwr.engine._allowGetForSafariButMakeForgeryEasier = "false";

/** The script prefix to strip in the case of scriptTagProtection. */
dwr.engine._scriptTagProtection = "throw 'allowScriptTagRemoting is false.';";

/** The default path to the DWR servlet */
dwr.engine._defaultPath = "/AuthoringHelianthus/dwr";

/** Do we use XHR for reverse ajax because we are not streaming? */
dwr.engine._pollWithXhr = "false";

/** The read page id that we calculate */
dwr.engine._scriptSessionId = null;

/** The function that we use to fetch/calculate a session id */
dwr.engine._getScriptSessionId = function() {
  if (dwr.engine._scriptSessionId == null) {
    dwr.engine._scriptSessionId = dwr.engine._origScriptSessionId + Math.floor(Math.random() * 1000);
  }
  return dwr.engine._scriptSessionId;
};

/** A function to call if something fails. */
dwr.engine._errorHandler = dwr.engine.defaultErrorHandler;

/** For debugging when something unexplained happens. */
dwr.engine._warningHandler = dwr.engine.defaultWarningHandler;

/** A function to be called before requests are marshalled. Can be null. */
dwr.engine._preHook = null;

/** A function to be called after replies are received. Can be null. */
dwr.engine._postHook = null;

/** An map of the batches that we have sent and are awaiting a reply on. */
dwr.engine._batches = {};

/** A count of the number of outstanding batches. Should be == to _batches.length unless prototype has messed things up */
dwr.engine._batchesLength = 0;

/** In ordered mode, the array of batches waiting to be sent */
dwr.engine._batchQueue = [];

/** What is the default rpc type */
dwr.engine._rpcType = dwr.engine.XMLHttpRequest;

/** What is the default remoting method (ie GET or POST) */
dwr.engine._httpMethod = "POST";

/** Do we attempt to ensure that calls happen in the order in which they were sent? */
dwr.engine._ordered = false;

/** Do we make the calls async? */
dwr.engine._async = true;

/** The current batch (if we are in batch mode) */
dwr.engine._batch = null;

/** The global timeout */
dwr.engine._timeout = 0;

/** ActiveX objects to use when we want to convert an xml string into a DOM object. */
dwr.engine._DOMDocument = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"];

/** The ActiveX objects to use when we want to do an XMLHttpRequest call. */
dwr.engine._XMLHTTP = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];

/** Are we doing comet or polling? */
dwr.engine._activeReverseAjax = false;

/** The iframe that we are using to poll */
dwr.engine._outstandingIFrames = [];

/** The xhr object that we are using to poll */
dwr.engine._pollReq = null;

/** How many milliseconds between internal comet polls */
dwr.engine._pollCometInterval = 200;

/** How many times have we re-tried to poll? */
dwr.engine._pollRetries = 0;
dwr.engine._maxPollRetries = 0;

/** Do we do a document.reload if we get a text/html reply? */
dwr.engine._textHtmlHandler = null;

/** If you wish to send custom headers with every request */
dwr.engine._headers = null;

/** If you wish to send extra custom request parameters with each request */
dwr.engine._parameters = null;

/** Undocumented interceptors - do not use */
dwr.engine._postSeperator = "\n";
dwr.engine._defaultInterceptor = function(data) { return data; };
dwr.engine._urlRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._contentRewriteHandler = dwr.engine._defaultInterceptor;
dwr.engine._replyRewriteHandler = dwr.engine._defaultInterceptor;

/** Batch ids allow us to know which batch the server is answering */
dwr.engine._nextBatchId = 0;

/** A list of the properties that need merging from calls to a batch */
dwr.engine._propnames = [ "rpcType", "httpMethod", "async", "timeout", "errorHandler", "warningHandler", "textHtmlHandler" ];

/** Do we stream, or can be hacked to do so? */
dwr.engine._partialResponseNo = 0;
dwr.engine._partialResponseYes = 1;
dwr.engine._partialResponseFlush = 2;

/**
 * @private Send a request. Called by the Javascript interface stub
 * @param path part of URL after the host and before the exec bit without leading or trailing /s
 * @param scriptName The class to execute
 * @param methodName The method on said class to execute
 * @param func The callback function to which any returned data should be passed
 *       if this is null, any returned data will be ignored
 * @param vararg_params The parameters to pass to the above class
 */
dwr.engine._execute = function(path, scriptName, methodName, vararg_params) {
  var singleShot = false;
  if (dwr.engine._batch == null) {
    dwr.engine.beginBatch();
    singleShot = true;
  }
  var batch = dwr.engine._batch;
  // To make them easy to manipulate we copy the arguments into an args array
  var args = [];
  for (var i = 0; i < arguments.length - 3; i++) {
    args[i] = arguments[i + 3];
  }
  // All the paths MUST be to the same servlet
  if (batch.path == null) {
    batch.path = path;
  }
  else {
    if (batch.path != path) {
      dwr.engine._handleError(batch, { name:"dwr.engine.multipleServlets", message:"Can't batch requests to multiple DWR Servlets." });
      return;
    }
  }
  // From the other params, work out which is the function (or object with
  // call meta-data) and which is the call parameters
  var callData;
  var lastArg = args[args.length - 1];
  if (typeof lastArg == "function" || lastArg == null) callData = { callback:args.pop() };
  else callData = args.pop();

  // Merge from the callData into the batch
  dwr.engine._mergeBatch(batch, callData);
  batch.handlers[batch.map.callCount] = {
    exceptionHandler:callData.exceptionHandler,
    callback:callData.callback
  };

  // Copy to the map the things that need serializing
  var prefix = "c" + batch.map.callCount + "-";
  batch.map[prefix + "scriptName"] = scriptName;
  batch.map[prefix + "methodName"] = methodName;
  batch.map[prefix + "id"] = batch.map.callCount;
  for (i = 0; i < args.length; i++) {
    dwr.engine._serializeAll(batch, [], args[i], prefix + "param" + i);
  }

  // Now we have finished remembering the call, we incr the call count
  batch.map.callCount++;
  if (singleShot) dwr.engine.endBatch();
};

/** @private Poll the server to see if there is any data waiting */
dwr.engine._poll = function(overridePath) {
  if (!dwr.engine._activeReverseAjax) return;

  var batch = dwr.engine._createBatch();
  batch.map.id = 0; // TODO: Do we need this??
  batch.map.callCount = 1;
  batch.isPoll = true;
  if (dwr.engine._pollWithXhr == "true") {
    batch.rpcType = dwr.engine.XMLHttpRequest;
    batch.map.partialResponse = dwr.engine._partialResponseNo;
  }
  else {
    if (navigator.userAgent.indexOf("Gecko/") != -1) {
      batch.rpcType = dwr.engine.XMLHttpRequest;
      batch.map.partialResponse = dwr.engine._partialResponseYes;
    }
    // else if (navigator.userAgent.indexOf("; MSIE")) {
    //   batch.rpcType = dwr.engine.IFrame;
    //   batch.map.partialResponse = dwr.engine._partialResponseYes;
    // }
    else if (navigator.userAgent.indexOf("Safari/")) {
      batch.rpcType = dwr.engine.XMLHttpRequest;
      batch.map.partialResponse = dwr.engine._partialResponseYes;
    }
    else {
      batch.rpcType = dwr.engine.XMLHttpRequest;
      batch.map.partialResponse = dwr.engine._partialResponseNo;
    }
  }
  batch.httpMethod = "POST";
  batch.async = true;
  batch.timeout = 0;
  batch.path = (overridePath) ? overridePath : dwr.engine._defaultPath;
  batch.preHooks = [];
  batch.postHooks = [];
  batch.errorHandler = dwr.engine._pollErrorHandler;
  batch.warningHandler = dwr.engine._pollErrorHandler;
  batch.handlers[0] = {
    callback:function(pause) {
      dwr.engine._pollRetries = 0;
      setTimeout("dwr.engine._poll()", pause);
    }
  };

  // Send the data
  dwr.engine._sendData(batch);
  if (batch.rpcType == dwr.engine.XMLHttpRequest && batch.map.partialResponse == dwr.engine._partialResponseYes) {
    dwr.engine._checkCometPoll();
  }
};

/** Try to recover from polling errors */
dwr.engine._pollErrorHandler = function(msg, ex) {
  // if anything goes wrong then just silently try again (up to 3x) after 10s
  dwr.engine._pollRetries++;
  dwr.engine._debug("Reverse Ajax poll failed (pollRetries=" + dwr.engine._pollRetries + "): " + ex.name + " : " + ex.message);
  if (dwr.engine._pollRetries < dwr.engine._maxPollRetries) {
    setTimeout("dwr.engine._poll()", 10000);
  }
  else {
    dwr.engine._activeReverseAjax = false;
    dwr.engine._debug("Giving up.");
  }
};

/** @private Generate a new standard batch */
dwr.engine._createBatch = function() {
  var batch = {
    map:{
      callCount:0,
      page:window.location.pathname + window.location.search,
      httpSessionId:dwr.engine._getJSessionId(),
      scriptSessionId:dwr.engine._getScriptSessionId()
    },
    charsProcessed:0, paramCount:0,
    parameters:{}, headers:{},
    isPoll:false, handlers:{}, preHooks:[], postHooks:[],
    rpcType:dwr.engine._rpcType,
    httpMethod:dwr.engine._httpMethod,
    async:dwr.engine._async,
    timeout:dwr.engine._timeout,
    errorHandler:dwr.engine._errorHandler,
    warningHandler:dwr.engine._warningHandler,
    textHtmlHandler:dwr.engine._textHtmlHandler
  };
  if (dwr.engine._preHook) batch.preHooks.push(dwr.engine._preHook);
  if (dwr.engine._postHook) batch.postHooks.push(dwr.engine._postHook);
  var propname, data;
  if (dwr.engine._headers) {
    for (propname in dwr.engine._headers) {
      data = dwr.engine._headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (dwr.engine._parameters) {
    for (propname in dwr.engine._parameters) {
      data = dwr.engine._parameters[propname];
      if (typeof data != "function") batch.parameters[propname] = data;
    }
  }
  return batch;
};

/** @private Take further options and merge them into */
dwr.engine._mergeBatch = function(batch, overrides) {
  var propname, data;
  for (var i = 0; i < dwr.engine._propnames.length; i++) {
    propname = dwr.engine._propnames[i];
    if (overrides[propname] != null) batch[propname] = overrides[propname];
  }
  if (overrides.preHook != null) batch.preHooks.unshift(overrides.preHook);
  if (overrides.postHook != null) batch.postHooks.push(overrides.postHook);
  if (overrides.headers) {
    for (propname in overrides.headers) {
      data = overrides.headers[propname];
      if (typeof data != "function") batch.headers[propname] = data;
    }
  }
  if (overrides.parameters) {
    for (propname in overrides.parameters) {
      data = overrides.parameters[propname];
      if (typeof data != "function") batch.map["p-" + propname] = "" + data;
    }
  }
};

/** @private What is our session id? */
dwr.engine._getJSessionId =  function() {
  var cookies = document.cookie.split(';');
  for (var i = 0; i < cookies.length; i++) {
    var cookie = cookies[i];
    while (cookie.charAt(0) == ' ') cookie = cookie.substring(1, cookie.length);
    if (cookie.indexOf(dwr.engine._sessionCookieName + "=") == 0) {
      return cookie.substring(dwr.engine._sessionCookieName.length + 1, cookie.length);
    }
  }
  return "";
};

/** @private Check for reverse Ajax activity */
dwr.engine._checkCometPoll = function() {
  for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
    var text = "";
    var iframe = dwr.engine._outstandingIFrames[i];
    try {
      text = dwr.engine._getTextFromCometIFrame(iframe);
    }
    catch (ex) {
      dwr.engine._handleWarning(iframe.batch, ex);
    }
    if (text != "") dwr.engine._processCometResponse(text, iframe.batch);
  }
  if (dwr.engine._pollReq) {
    var req = dwr.engine._pollReq;
    var text = req.responseText;
    if (text != null) dwr.engine._processCometResponse(text, req.batch);
  }

  // If the poll resources are still there, come back again
  if (dwr.engine._outstandingIFrames.length > 0 || dwr.engine._pollReq) {
    setTimeout("dwr.engine._checkCometPoll()", dwr.engine._pollCometInterval);
  }
};

/** @private Extract the whole (executed an all) text from the current iframe */
dwr.engine._getTextFromCometIFrame = function(frameEle) {
  var body = frameEle.contentWindow.document.body;
  if (body == null) return "";
  var text = body.innerHTML;
  // We need to prevent IE from stripping line feeds
  if (text.indexOf("<PRE>") == 0 || text.indexOf("<pre>") == 0) {
    text = text.substring(5, text.length - 7);
  }
  return text;
};

/** @private Some more text might have come in, test and execute the new stuff */
dwr.engine._processCometResponse = function(response, batch) {
  if (batch.charsProcessed == response.length) return;
  if (response.length == 0) {
    batch.charsProcessed = 0;
    return;
  }

  var firstStartTag = response.indexOf("//#DWR-START#", batch.charsProcessed);
  if (firstStartTag == -1) {
    // dwr.engine._debug("No start tag (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed) + "'");
    batch.charsProcessed = response.length;
    return;
  }
  // if (firstStartTag > 0) {
  //   dwr.engine._debug("Start tag not at start (search from " + batch.charsProcessed + "). skipping '" + response.substring(batch.charsProcessed, firstStartTag) + "'");
  // }

  var lastEndTag = response.lastIndexOf("//#DWR-END#");
  if (lastEndTag == -1) {
    // dwr.engine._debug("No end tag. unchanged charsProcessed=" + batch.charsProcessed);
    return;
  }

  // Skip the end tag too for next time, remembering CR and LF
  if (response.charCodeAt(lastEndTag + 11) == 13 && response.charCodeAt(lastEndTag + 12) == 10) {
    batch.charsProcessed = lastEndTag + 13;
  }
  else {
    batch.charsProcessed = lastEndTag + 11;
  }

  var exec = response.substring(firstStartTag + 13, lastEndTag);

  dwr.engine._receivedBatch = batch;
  dwr.engine._eval(exec);
  dwr.engine._receivedBatch = null;
};

/** @private Actually send the block of data in the batch object. */
dwr.engine._sendData = function(batch) {
  batch.map.batchId = dwr.engine._nextBatchId;
  dwr.engine._nextBatchId++;
  dwr.engine._batches[batch.map.batchId] = batch;
  dwr.engine._batchesLength++;
  batch.completed = false;

  for (var i = 0; i < batch.preHooks.length; i++) {
    batch.preHooks[i]();
  }
  batch.preHooks = null;
  // Set a timeout
  if (batch.timeout && batch.timeout != 0) {
    batch.interval = setInterval(function() { dwr.engine._abortRequest(batch); }, batch.timeout);
  }
  // Get setup for XMLHttpRequest if possible
  if (batch.rpcType == dwr.engine.XMLHttpRequest) {
    if (window.XMLHttpRequest) {
      batch.req = new XMLHttpRequest();
    }
    // IE5 for the mac claims to support window.ActiveXObject, but throws an error when it's used
    else if (window.ActiveXObject && !(navigator.userAgent.indexOf("Mac") >= 0 && navigator.userAgent.indexOf("MSIE") >= 0)) {
      batch.req = dwr.engine._newActiveXObject(dwr.engine._XMLHTTP);
    }
  }

  var prop, request;
  if (batch.req) {
    // Proceed using XMLHttpRequest
    if (batch.async) {
      batch.req.onreadystatechange = function() {
        if (typeof dwr != 'undefined') dwr.engine._stateChange(batch);
      };
    }
    // If we're polling, record this for monitoring
    if (batch.isPoll) {
      dwr.engine._pollReq = batch.req;
      // In IE XHR is an ActiveX control so you can't augment it like this
      if (!document.all) batch.req.batch = batch;
    }
    // Workaround for Safari 1.x POST bug
    var indexSafari = navigator.userAgent.indexOf("Safari/");
    if (indexSafari >= 0) {
      var version = navigator.userAgent.substring(indexSafari + 7);
      if (parseInt(version, 10) < 400) {
        if (dwr.engine._allowGetForSafariButMakeForgeryEasier == "true") batch.httpMethod = "GET";
        else dwr.engine._handleWarning(batch, { name:"dwr.engine.oldSafari", message:"Safari GET support disabled. See getahead.org/dwr/server/servlet and allowGetForSafariButMakeForgeryEasier." });
      }
    }
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    try {
      batch.req.open(batch.httpMethod, request.url, batch.async);
      try {
        for (prop in batch.headers) {
          var value = batch.headers[prop];
          if (typeof value == "string") batch.req.setRequestHeader(prop, value);
        }
        if (!batch.headers["Content-Type"]) batch.req.setRequestHeader("Content-Type", "text/plain");
      }
      catch (ex) {
        dwr.engine._handleWarning(batch, ex);
      }
      batch.req.send(request.body);
      if (!batch.async) dwr.engine._stateChange(batch);
    }
    catch (ex) {
      dwr.engine._handleError(batch, ex);
    }
  }
  else if (batch.rpcType != dwr.engine.ScriptTag) {
    var idname = batch.isPoll ? "dwr-if-poll-" + batch.map.batchId : "dwr-if-" + batch.map["c0-id"];
    // on IE try to use the htmlfile activex control
    if (batch.isPoll && window.ActiveXObject) {
      batch.htmlfile = new window.ActiveXObject("htmlfile");
      batch.htmlfile.open();
      batch.htmlfile.write("<html>");
      //batch.htmlfile.write("<script>document.domain='" + document.domain + "';</script>");
      batch.htmlfile.write("<div><iframe className='wibble' src='javascript:void(0)' id='" + idname + "' name='" + idname + "' onload='dwr.engine._iframeLoadingComplete(" + batch.map.batchId + ");'></iframe></div>");
      batch.htmlfile.write("</html>");
      batch.htmlfile.close();
      batch.htmlfile.parentWindow.dwr = dwr;
      batch.document = batch.htmlfile;
    }
    else {
      batch.div = document.createElement("div");
      // Add the div to the document first, otherwise IE 6 will ignore onload handler.
      document.body.appendChild(batch.div);
      batch.div.innerHTML = "<iframe src='javascript:void(0)' frameborder='0' style='width:0px;height:0px;border:0;' id='" + idname + "' name='" + idname + "' onload='dwr.engine._iframeLoadingComplete (" + batch.map.batchId + ");'></iframe>";
      batch.document = document;
    }
    batch.iframe = batch.document.getElementById(idname);
    batch.iframe.batch = batch;
    batch.mode = batch.isPoll ? dwr.engine._ModeHtmlPoll : dwr.engine._ModeHtmlCall;
    if (batch.isPoll) dwr.engine._outstandingIFrames.push(batch.iframe);
    request = dwr.engine._constructRequest(batch);
    if (batch.httpMethod == "GET") {
      batch.iframe.setAttribute("src", request.url);
    }
    else {
      batch.form = batch.document.createElement("form");
      batch.form.setAttribute("id", "dwr-form");
      batch.form.setAttribute("action", request.url);
      batch.form.setAttribute("target", idname);
      batch.form.target = idname;
      batch.form.setAttribute("method", batch.httpMethod);
      for (prop in batch.map) {
        var value = batch.map[prop];
        if (typeof value != "function") {
          var formInput = batch.document.createElement("input");
          formInput.setAttribute("type", "hidden");
          formInput.setAttribute("name", prop);
          formInput.setAttribute("value", value);
          batch.form.appendChild(formInput);
        }
      }
      batch.document.body.appendChild(batch.form);
      batch.form.submit();
    }
  }
  else {
    batch.httpMethod = "GET"; // There's no such thing as ScriptTag using POST
    batch.mode = batch.isPoll ? dwr.engine._ModePlainPoll : dwr.engine._ModePlainCall;
    request = dwr.engine._constructRequest(batch);
    batch.script = document.createElement("script");
    batch.script.id = "dwr-st-" + batch.map["c0-id"];
    batch.script.src = request.url;
    document.body.appendChild(batch.script);
  }
};

dwr.engine._ModePlainCall = "/call/plaincall/";
dwr.engine._ModeHtmlCall = "/call/htmlcall/";
dwr.engine._ModePlainPoll = "/call/plainpoll/";
dwr.engine._ModeHtmlPoll = "/call/htmlpoll/";

/** @private Work out what the URL should look like */
dwr.engine._constructRequest = function(batch) {
  // A quick string to help people that use web log analysers
  var request = { url:batch.path + batch.mode, body:null };
  if (batch.isPoll == true) {
    request.url += "ReverseAjax.dwr";
  }
  else if (batch.map.callCount == 1) {
    request.url += batch.map["c0-scriptName"] + "." + batch.map["c0-methodName"] + ".dwr";
  }
  else {
    request.url += "Multiple." + batch.map.callCount + ".dwr";
  }
  // Play nice with url re-writing
  var sessionMatch = location.href.match(/jsessionid=([^?]+)/);
  if (sessionMatch != null) {
    request.url += ";jsessionid=" + sessionMatch[1];
  }

  var prop;
  if (batch.httpMethod == "GET") {
    // Some browsers (Opera/Safari2) seem to fail to convert the callCount value
    // to a string in the loop below so we do it manually here.
    batch.map.callCount = "" + batch.map.callCount;
    request.url += "?";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.url += encodeURIComponent(prop) + "=" + encodeURIComponent(batch.map[prop]) + "&";
      }
    }
    request.url = request.url.substring(0, request.url.length - 1);
  }
  else {
    // PERFORMANCE: for iframe mode this is thrown away.
    request.body = "";
    for (prop in batch.map) {
      if (typeof batch.map[prop] != "function") {
        request.body += prop + "=" + batch.map[prop] + dwr.engine._postSeperator;
      }
    }
    request.body = dwr.engine._contentRewriteHandler(request.body);
  }
  request.url = dwr.engine._urlRewriteHandler(request.url);
  return request;
};

/** @private Called by XMLHttpRequest to indicate that something has happened */
dwr.engine._stateChange = function(batch) {
  var toEval;

  if (batch.completed) {
    dwr.engine._debug("Error: _stateChange() with batch.completed");
    return;
  }

  var req = batch.req;
  try {
    if (req.readyState != 4) return;
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
    // It's broken - clear up and forget this call
    dwr.engine._clearUp(batch);
    return;
  }

  try {
    var reply = req.responseText;
    reply = dwr.engine._replyRewriteHandler(reply);
    var status = req.status; // causes Mozilla to except on page moves

    if (reply == null || reply == "") {
      dwr.engine._handleWarning(batch, { name:"dwr.engine.missingData", message:"No data received from server" });
    }
    else if (status != 200) {
      dwr.engine._handleError(batch, { name:"dwr.engine.http." + status, message:req.statusText });
    }
    else {
      var contentType = req.getResponseHeader("Content-Type");
      if (!contentType.match(/^text\/plain/) && !contentType.match(/^text\/javascript/)) {
        if (contentType.match(/^text\/html/) && typeof batch.textHtmlHandler == "function") {
          batch.textHtmlHandler();
        }
        else {
          dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidMimeType", message:"Invalid content type: '" + contentType + "'" });
        }
      }
      else {
        // Comet replies might have already partially executed
        if (batch.isPoll && batch.map.partialResponse == dwr.engine._partialResponseYes) {
          dwr.engine._processCometResponse(reply, batch);
        }
        else {
          if (reply.search("//#DWR") == -1) {
            dwr.engine._handleWarning(batch, { name:"dwr.engine.invalidReply", message:"Invalid reply from server" });
          }
          else {
            toEval = reply;
          }
        }
      }
    }
  }
  catch (ex) {
    dwr.engine._handleWarning(batch, ex);
  }

  dwr.engine._callPostHooks(batch);

  // Outside of the try/catch so errors propogate normally:
  dwr.engine._receivedBatch = batch;
  if (toEval != null) toEval = toEval.replace(dwr.engine._scriptTagProtection, "");
  dwr.engine._eval(toEval);
  dwr.engine._receivedBatch = null;
  dwr.engine._validateBatch(batch);
  dwr.engine._clearUp(batch);
};

/**
 * @private This function is invoked when a batch reply is received.
 * It checks that there is a response for every call in the batch. Otherwise,
 * an error will be signaled (a call without a response indicates that the 
 * server failed to send complete batch response). 
 */
dwr.engine._validateBatch = function(batch) {
  // If some call left unreplied, report an error.
  if (!batch.completed) {
    for (var i = 0; i < batch.map.callCount; i++) {
      if (batch.handlers[i] != null) {
        dwr.engine._handleWarning(batch, { name:"dwr.engine.incompleteReply", message:"Incomplete reply from server" });
        break;
      }
    }
  }
}

/** @private Called from iframe onload, check batch using batch-id */
dwr.engine._iframeLoadingComplete = function(batchId) {
  // dwr.engine._checkCometPoll();
  var batch = dwr.engine._batches[batchId];
  if (batch) dwr.engine._validateBatch(batch);
}

/** @private Called by the server: Execute a callback */
dwr.engine._remoteHandleCallback = function(batchId, callId, reply) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) {
    dwr.engine._debug("Warning: batch == null in remoteHandleCallback for batchId=" + batchId, true);
    return;
  }
  // Error handlers inside here indicate an error that is nothing to do
  // with DWR so we handle them differently.
  try {
    var handlers = batch.handlers[callId];
    batch.handlers[callId] = null;
    if (!handlers) {
      dwr.engine._debug("Warning: Missing handlers. callId=" + callId, true);
    }
    else if (typeof handlers.callback == "function") handlers.callback(reply);
  }
  catch (ex) {
    dwr.engine._handleError(batch, ex);
  }
};

/** @private Called by the server: Handle an exception for a call */
dwr.engine._remoteHandleException = function(batchId, callId, ex) {
  var batch = dwr.engine._batches[batchId];
  if (batch == null) { dwr.engine._debug("Warning: null batch in remoteHandleException", true); return; }
  var handlers = batch.handlers[callId];
  batch.handlers[callId] = null;
  if (handlers == null) { dwr.engine._debug("Warning: null handlers in remoteHandleException", true); return; }
  if (ex.message == undefined) ex.message = "";
  if (typeof handlers.exceptionHandler == "function") handlers.exceptionHandler(ex.message, ex);
  else if (typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
};

/** @private Called by the server: The whole batch is broken */
dwr.engine._remoteHandleBatchException = function(ex, batchId) {
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: Reverse ajax should not be used */
dwr.engine._remotePollCometDisabled = function(ex, batchId) {
  dwr.engine.setActiveReverseAjax(false);
  var searchBatch = (dwr.engine._receivedBatch == null && batchId != null);
  if (searchBatch) {
    dwr.engine._receivedBatch = dwr.engine._batches[batchId];
  }
  if (ex.message == undefined) ex.message = "";
  dwr.engine._handleError(dwr.engine._receivedBatch, ex);
  if (searchBatch) {
    dwr.engine._receivedBatch = null;
    dwr.engine._clearUp(dwr.engine._batches[batchId]);
  }
};

/** @private Called by the server: An IFrame reply is about to start */
dwr.engine._remoteBeginIFrameResponse = function(iframe, batchId) {
  if (iframe != null) dwr.engine._receivedBatch = iframe.batch;
  dwr.engine._callPostHooks(dwr.engine._receivedBatch);
};

/** @private Called by the server: An IFrame reply is just completing */
dwr.engine._remoteEndIFrameResponse = function(batchId) {
  dwr.engine._clearUp(dwr.engine._receivedBatch);
  dwr.engine._receivedBatch = null;
};

/** @private This is a hack to make the context be this window */
dwr.engine._eval = function(script) {
  if (script == null) return null;
  if (script == "") { dwr.engine._debug("Warning: blank script", true); return null; }
  // dwr.engine._debug("Exec: [" + script + "]", true);
  return eval(script);
};

/** @private Called as a result of a request timeout */
dwr.engine._abortRequest = function(batch) {
  if (batch && !batch.completed) {
    clearInterval(batch.interval);
    dwr.engine._clearUp(batch);
    if (batch.req) batch.req.abort();
    dwr.engine._handleError(batch, { name:"dwr.engine.timeout", message:"Timeout" });
  }
};

/** @private call all the post hooks for a batch */
dwr.engine._callPostHooks = function(batch) {
  if (batch.postHooks) {
    for (var i = 0; i < batch.postHooks.length; i++) {
      batch.postHooks[i]();
    }
    batch.postHooks = null;
  }
};

/** @private A call has finished by whatever means and we need to shut it all down. */
dwr.engine._clearUp = function(batch) {
  if (!batch) { dwr.engine._debug("Warning: null batch in dwr.engine._clearUp()", true); return; }
  if (batch.completed == "true") { dwr.engine._debug("Warning: Double complete", true); return; }

  // IFrame tidyup
  if (batch.div) batch.div.parentNode.removeChild(batch.div);
  if (batch.iframe) {
    // If this is a poll frame then stop comet polling
    for (var i = 0; i < dwr.engine._outstandingIFrames.length; i++) {
      if (dwr.engine._outstandingIFrames[i] == batch.iframe) {
        dwr.engine._outstandingIFrames.splice(i, 1);
      }
    }
    batch.iframe.parentNode.removeChild(batch.iframe);
  }
  if (batch.form) batch.form.parentNode.removeChild(batch.form);

  // XHR tidyup: avoid IE handles increase
  if (batch.req) {
    // If this is a poll frame then stop comet polling
    if (batch.req == dwr.engine._pollReq) dwr.engine._pollReq = null;
    delete batch.req;
  }

  if (batch.map && batch.map.batchId) {
    delete dwr.engine._batches[batch.map.batchId];
    dwr.engine._batchesLength--;
  }

  batch.completed = true;

  // If there is anything on the queue waiting to go out, then send it.
  // We don't need to check for ordered mode, here because when ordered mode
  // gets turned off, we still process *waiting* batches in an ordered way.
  if (dwr.engine._batchQueue.length != 0) {
    var sendbatch = dwr.engine._batchQueue.shift();
    dwr.engine._sendData(sendbatch);
  }
};

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleError = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.errorHandler == "function") batch.errorHandler(ex.message, ex);
  else if (dwr.engine._errorHandler) dwr.engine._errorHandler(ex.message, ex);
  if (batch) dwr.engine._clearUp(batch);
};

/** @private Generic error handling routing to save having null checks everywhere */
dwr.engine._handleWarning = function(batch, ex) {
  if (typeof ex == "string") ex = { name:"unknown", message:ex };
  if (ex.message == null) ex.message = "";
  if (ex.name == null) ex.name = "unknown";
  if (batch && typeof batch.warningHandler == "function") batch.warningHandler(ex.message, ex);
  else if (dwr.engine._warningHandler) dwr.engine._warningHandler(ex.message, ex);
  if (batch) dwr.engine._clearUp(batch);
};

/**
 * @private Marshall a data item
 * @param batch A map of variables to how they have been marshalled
 * @param referto An array of already marshalled variables to prevent recurrsion
 * @param data The data to be marshalled
 * @param name The name of the data being marshalled
 */
dwr.engine._serializeAll = function(batch, referto, data, name) {
  if (data == null) {
    batch.map[name] = "null:null";
    return;
  }

  switch (typeof data) {
  case "boolean":
    batch.map[name] = "boolean:" + data;
    break;
  case "number":
    batch.map[name] = "number:" + data;
    break;
  case "string":
    batch.map[name] = "string:" + encodeURIComponent(data);
    break;
  case "object":
    if (data instanceof String) batch.map[name] = "String:" + encodeURIComponent(data);
    else if (data instanceof Boolean) batch.map[name] = "Boolean:" + data;
    else if (data instanceof Number) batch.map[name] = "Number:" + data;
    else if (data instanceof Date) batch.map[name] = "Date:" + data.getTime();
    else if (data && data.join) batch.map[name] = dwr.engine._serializeArray(batch, referto, data, name);
    else batch.map[name] = dwr.engine._serializeObject(batch, referto, data, name);
    break;
  case "function":
    // We just ignore functions.
    break;
  default:
    dwr.engine._handleWarning(null, { name:"dwr.engine.unexpectedType", message:"Unexpected type: " + typeof data + ", attempting default converter." });
    batch.map[name] = "default:" + data;
    break;
  }
};

/** @private Have we already converted this object? */
dwr.engine._lookup = function(referto, data, name) {
  var lookup;
  // Can't use a map: getahead.org/ajax/javascript-gotchas
  for (var i = 0; i < referto.length; i++) {
    if (referto[i].data == data) {
      lookup = referto[i];
      break;
    }
  }
  if (lookup) return "reference:" + lookup.name;
  referto.push({ data:data, name:name });
  return null;
};

/** @private Marshall an object */
dwr.engine._serializeObject = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  // This check for an HTML is not complete, but is there a better way?
  // Maybe we should add: data.hasChildNodes typeof "function" == true
  if (data.nodeName && data.nodeType) {
    return dwr.engine._serializeXml(batch, referto, data, name);
  }

  // treat objects as an associative arrays
  var reply = "Object_" + dwr.engine._getObjectClassName(data) + ":{";
  var element;
  for (element in data) {
    if (typeof data[element] != "function") {
      batch.paramCount++;
      var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
      dwr.engine._serializeAll(batch, referto, data[element], childName);

      reply += encodeURIComponent(element) + ":reference:" + childName + ", ";
    }
  }

  if (reply.substring(reply.length - 2) == ", ") {
    reply = reply.substring(0, reply.length - 2);
  }
  reply += "}";

  return reply;
};

/** @private Returns the classname of supplied argument obj */
dwr.engine._errorClasses = { "Error":Error, "EvalError":EvalError, "RangeError":RangeError, "ReferenceError":ReferenceError, "SyntaxError":SyntaxError, "TypeError":TypeError, "URIError":URIError };
dwr.engine._getObjectClassName = function(obj) {
  // Try to find the classname by stringifying the object's constructor
  // and extract <class> from "function <class>".
  if (obj && obj.constructor && obj.constructor.toString)
  {
    var str = obj.constructor.toString();
    var regexpmatch = str.match(/function\s+(\w+)/);
    if (regexpmatch && regexpmatch.length == 2) {
      return regexpmatch[1];
    }
  }

  // Now manually test against the core Error classes, as these in some 
  // browsers successfully match to the wrong class in the 
  // Object.toString() test we will do later
  if (obj && obj.constructor) {
    for (var errorname in dwr.engine._errorClasses) {
      if (obj.constructor == dwr.engine._errorClasses[errorname]) return errorname;
    }
  }

  // Try to find the classname by calling Object.toString() on the object
  // and extracting <class> from "[object <class>]"
  if (obj) {
    var str = Object.prototype.toString.call(obj);
    var regexpmatch = str.match(/\[object\s+(\w+)/);
    if (regexpmatch && regexpmatch.length==2) {
      return regexpmatch[1];
    }
  }

  // Supplied argument was probably not an object, but what is better?
  return "Object";
};

/** @private Marshall an object */
dwr.engine._serializeXml = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var output;
  if (window.XMLSerializer) output = new XMLSerializer().serializeToString(data);
  else if (data.toXml) output = data.toXml;
  else output = data.innerHTML;

  return "XML:" + encodeURIComponent(output);
};

/** @private Marshall an array */
dwr.engine._serializeArray = function(batch, referto, data, name) {
  var ref = dwr.engine._lookup(referto, data, name);
  if (ref) return ref;

  var reply = "Array:[";
  for (var i = 0; i < data.length; i++) {
    if (i != 0) reply += ",";
    batch.paramCount++;
    var childName = "c" + dwr.engine._batch.map.callCount + "-e" + batch.paramCount;
    dwr.engine._serializeAll(batch, referto, data[i], childName);
    reply += "reference:";
    reply += childName;
  }
  reply += "]";

  return reply;
};

/** @private Convert an XML string into a DOM object. */
dwr.engine._unserializeDocument = function(xml) {
  var dom;
  if (window.DOMParser) {
    var parser = new DOMParser();
    dom = parser.parseFromString(xml, "text/xml");
    if (!dom.documentElement || dom.documentElement.tagName == "parsererror") {
      var message = dom.documentElement.firstChild.data;
      message += "\n" + dom.documentElement.firstChild.nextSibling.firstChild.data;
      throw message;
    }
    return dom;
  }
  else if (window.ActiveXObject) {
    dom = dwr.engine._newActiveXObject(dwr.engine._DOMDocument);
    dom.loadXML(xml); // What happens on parse fail with IE?
    return dom;
  }
  else {
    var div = document.createElement("div");
    div.innerHTML = xml;
    return div;
  }
};

/** @param axarray An array of strings to attempt to create ActiveX objects from */
dwr.engine._newActiveXObject = function(axarray) {
  var returnValue;  
  for (var i = 0; i < axarray.length; i++) {
    try {
      returnValue = new ActiveXObject(axarray[i]);
      break;
    }
    catch (ex) { /* ignore */ }
  }
  return returnValue;
};

/** @private Used internally when some message needs to get to the programmer */
dwr.engine._debug = function(message, stacktrace) {
  var written = false;
  try {
    if (window.console) {
      if (stacktrace && window.console.trace) window.console.trace();
      window.console.log(message);
      written = true;
    }
    else if (window.opera && window.opera.postError) {
      window.opera.postError(message);
      written = true;
    }
  }
  catch (ex) { /* ignore */ }

  if (!written) {
    var debug = document.getElementById("dwr-debug");
    if (debug) {
      var contents = message + "<br/>" + debug.innerHTML;
      if (contents.length > 2048) contents = contents.substring(0, 2048);
      debug.innerHTML = contents;
    }
  }
};

var HDAChrome = {
    textMode : false,
    lastFormAction: null,

    minimumNumberOfVisibleButtons: 4,
    lastMenuToRender: false,
    
    init : function(player) {
        this.player = player;

        if (typeof(HDATheme) != "undefined" && HDATheme.init) {
            HDATheme.init();
        }
        if (typeof(HDAClient) != "undefined" && HDAClient.init) {
            HDAClient.init();
        }

        /* Get all face control buttons */
        this.controls = new Object();
        if (getElementByIdHDA('hda-faceControls')) {
            var tmp = getElementByIdHDA('hda-faceControls').getElementsByTagName('li');
            for (var i = 0; i < tmp.length; i++) {
                var controlName = HDAUtils.camelize(tmp[i].id.replace(
                        'hda-control', ''));
                // FIXME: eventually remove the "element" subobject
                this.controls[controlName] = new Object();
                this.controls[controlName].element = tmp[i];
            }

            /* Get all menu buttons */
            this.menuButtons = this.getMenuButtons();

            this.reset();
            if (getElementByIdHDA("hda-controlVideoOn"))
                getElementByIdHDA("hda-controlVideoOn").style.display = "none";
            if (getElementByIdHDA("hda-controlVideoOff"))
                getElementByIdHDA("hda-controlVideoOff").style.display = "";

            for (control in this.controls) {
                var element = this.controls[control].element;
                element.onclick = (function(evt) {
                    var c = HDAUtils.DOMEvent.getTarget(evt);
                    if (!HDAUtils.element.hasClassName(c, 'active'))
                        return;
                    /*
                     * Converts element id name to the relative event name
                     * hda-controlToggleVideo -> faceToggleVideo
                     */
                    var faceEvt = c.id.replace('hda-control', 'face');
                    if (this.events[faceEvt]) {
                        var cb = "_on" + faceEvt.substring(0, 1).toUpperCase()
                                + faceEvt.substring(1);
                        if (this[cb]) {
                            this[cb]();
                        }
                        HDAPlayer[cb]();
                        this.events[faceEvt].fire();
                    }
                }).bind(this);

                element.onmouseover = (function(evt) {
                    var c = HDAUtils.DOMEvent.getTarget(evt);
                    if (!HDAUtils.element.hasClassName(c, 'active'))
                        return;
                    HDAUtils.element.addClassName(c, 'hover');
                }).bind(this);

                element.onmouseout = (function(evt) {
                    var c = HDAUtils.DOMEvent.getTarget(evt);
                    if (!HDAUtils.element.hasClassName(c, 'active'))
                        return;
                    HDAUtils.element.removeClassName(c, 'hover');
                }).bind(this);
            }
        }
        if (getElementByIdHDA("hda-helpSpot")) {
            getElementByIdHDA("hda-helpSpot").onclick = (function(evt) {
                HDAPlayer._onFaceHelp();
                this.events["faceHelp"].fire();
            }).bind(this);
        }
    },

    getMenuButtons : function() {
        var menuButtons = [];
        var alltds = getElementByIdHDA("hda-menu-body").getElementsByTagName('td');
        for (var i = 0; i < alltds.length; i++) {
            if (HDAUtils.element.hasClassName(alltds[i], "hda-button")) {
                menuButtons.push(alltds[i]);
            }
        }
        return menuButtons;
    },
    
    removeMenuButtons: function() {
        var menubody = getElementByIdHDA("hda-menu-body");
        if(menubody) {
            while(menubody.childNodes.length > 0) {
                menubody.removeChild(menubody.childNodes[0]);
            }
        }
        this.menuButtons = [];
    },
    
    createMenuButton: function(id, text, disabled) {
        var td = document.createElement('td');
        td.id = id;
        HDAUtils.element.addClassName(td, 'hda-button');
        HDAUtils.element.addClassName(td, disabled === true ? '' : 'active');
        td.innerHTML = text;
        td.onmouseover = function(evt) {
            HDAUtils.element.addClassName(HDAUtils.DOMEvent.getTarget(evt), 'hover');
        };
        td.onmouseout = function(evt) {
            HDAUtils.element.removeClassName(HDAUtils.DOMEvent.getTarget(evt), 'hover');
        };
        td.onmousedown = function(evt) {
            HDAUtils.element.addClassName(HDAUtils.DOMEvent.getTarget(evt), 'selected');
        };
        td.onmouseup = function(evt) {
            HDAUtils.element.removeClassName(HDAUtils.DOMEvent.getTarget(evt), 'selected');
        };
        if(disabled !== true) {
            td.onclick = (function(evt) {
                this.resetControls();
                el = HDAUtils.DOMEvent.getTarget(evt);
                var choice = parseInt(el.id.replace('hda-menuButton_', '')) - 1;
                this._onMenuChoice(choice);
                HDAPlayer._onMenuChoice(choice);
                this.events.menuChoice.fire(choice);
            }).bind(this);
        }
        return td;
    },

    addMenuButton: function(button, isLast) {
        var menubody = getElementByIdHDA("hda-menu-body");
        if(menubody.childNodes.length === 0) {
            HDAUtils.element.addClassName(button, 'first');
        }
        if(isLast === true) {
            HDAUtils.element.addClassName(button, 'last');
        }
        var tr = document.createElement('tr');
        tr.appendChild(button);
        menubody.appendChild(tr);
    },
    
    reset : function(coldRestart) {
        this.resetMenu();
        this.resetForm();
        this.resetControls();

        this.textMode = false;

        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.height = "";
        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.width = "";

        if(getElementByIdHDA('hda-alternativeText'))
            getElementByIdHDA('hda-alternativeText').style.display = "none";
        if(getElementByIdHDA("hda-controlVideoOff"))
            getElementByIdHDA("hda-controlVideoOff").style.display = "";
        if(getElementByIdHDA("hda-controlVideoOn"))
            getElementByIdHDA("hda-controlVideoOn").style.display = "none";
        HDAFacePlayer.engine = HDAFlashFacePlayer;
        if(this.controls.videoOff && this.controls.videoOff.element)
            HDAUtils.element.addClassName(this.controls.videoOff.element, 'active');

        if (coldRestart) {
            if(getElementByIdHDA('hda-controlLastMenu'))
                HDAUtils.element.removeClassName(getElementByIdHDA('hda-controlLastMenu'), 'active');
            if(this.controls.repeat && this.controls.repeat.element)
                HDAUtils.element.removeClassName(this.controls.repeat.element, 'active');
        }
    },

    resetMenu : function() {
        this.menuButtons = this.getMenuButtons();
        this.resetOldMenu();
    },
    
    resetOldMenu: function() {
        for (var i = 0, s = this.menuButtons.length; i < s; i++) {
            b = this.menuButtons[i];
            b.innerHTML = '';
            HDAUtils.element.removeClassName(b, 'active');
            b.onclick = function() { return false; };
        };
        
        // Reset lastMenuButton
        if (this.controls.lastMenu){
        	HDAUtils.element.removeClassName(this.controls.lastMenu.element, 'active');
        }
    },
    
    resetControls : function() {
        for (c in this.controls) {
            // If lastmenu is active, we leave it as it is
            if (this.controls[c].element.id != 'hda-controlLastMenu')
                if(this.controls[c].element)
                    HDAUtils.element.removeClassName(this.controls[c].element, 'active');
        }

        if(getElementByIdHDA("hda-controlVideoOff"))
            HDAUtils.element.addClassName(getElementByIdHDA("hda-controlVideoOff"), 'active');
        if(getElementByIdHDA("hda-controlVideoOn"))
            HDAUtils.element.addClassName(getElementByIdHDA("hda-controlVideoOn"), 'active');
    },

    displayMenu : function(menu) {
        this.removeMenuButtons();
        if(this.minimumNumberOfVisibleButtons === 0)
            var max = menu.children.length;
        else
            var max = this.minimumNumberOfVisibleButtons;
        
        for (var i = 0; i < max; i++) {
            if(i < menu.children.length) {
                console.debug("[CH] Adding menu item %o", menu.children[i]);
                var button = this.createMenuButton('hda-menuButton_' + (i + 1), menu.children[i].content);
            } else {
                var button = this.createMenuButton('hda-menuButton_' + (i + 1), '', true);
            }
            this.menuButtons.push(button);
            this.addMenuButton(button, i === menu.children.length-1);
        }
    },

    setAlternativeTextHTML : function(html) {
        if(getElementByIdHDA('hda-alternativeText'))
          getElementByIdHDA('hda-alternativeText').innerHTML = html;
    },

    getAlternativeTextHTML : function(html) {
        if(getElementByIdHDA('hda-alternativeText'))
          return getElementByIdHDA('hda-alternativeText').innerHTML;
    },

    appendAlternativeText : function(text) {
        if (!text)
            return;
        if(getElementByIdHDA('hda-alternativeText'))
            getElementByIdHDA('hda-alternativeText').innerHTML += "<p>" + text + "<p>";
    },

    setCaption : function(text) {
        if (!text)
            text = '';
        if (text == '*')
            return;
        getElementByIdHDA('hda-textArea').innerHTML = text;
    },

    getCaption : function(html) {
        return getElementByIdHDA('hda-textArea').innerHTML;
    },

    appendErrorMessage : function(error, message) {
        if (message) {
            var text = message;
        } else {
            if (error == 'null-response') {
                var err = "hda-noConnectionErrorMessage";
            } else {
                var err = "hda-noVideoErrorMessage";
            }
            var text = getElementByIdHDA(err) ? getElementByIdHDA(err).innerHTML : "Connection error";
        }
        getElementByIdHDA('hda-alternativeText').innerHTML = "<p class=\"hda-errorMessage\">"
                + text + "<p>" + getElementByIdHDA('hda-alternativeText').innerHTML;
    },

    clearAlternativeText : function() {
        if(getElementByIdHDA('hda-alternativeText'))
            getElementByIdHDA('hda-alternativeText').innerHTML = "";
    },
    
    renderFormField: function(action){
    	;;; console.debug("[CH] Requested a %o field", action.params['type']);
    	var theResult = "";
    	
    	switch (action.params['type']){
    	case 'hda-text-area':
    		theResult = '<label for="' +  action.target
    			+ '" class="hda-label">' + action.content 
    			+ '</label>' + 
    			'<textarea id="' + action.target 
    			+ '" name="' + action.target 
    			+ '" class="hda-text-area" cols="20" rows="'+ action.params['numrows']
    			+'"></textarea>';
    		break;
    	// TODO Add other field type here
    	default:
    		;;; console.warn("[CH] Unknown type field: %o. This should not happen!", action.params['type']);
    		break;
    	}
    	
    	return theResult;
    },
    
    renderSubmitButton: function(action){
    	;;; console.debug("[CH] Requested a %o field", action.params['type']);
    	var theResult = '<input type="button" id="hda-submit-button" name="hda-submit-button" class="hda-submit-button" onclick="javascript:HDAChrome.submitForm()" value="' + action.content 
    	+ '" />';
    	return theResult;
    },
    
    renderForm: function (action) {
    	this.lastFormAction = action;
    	var theForm = '<form name="hda-generated-form">';
    	var formElements = action.children;
    	var firstFieldElement = null;
    	for (var i = 0; i < formElements.length; i++){
    		switch (formElements[i].command){
    		case 'form-field':
    			if (firstFieldElement == null){
    				firstFieldElement = formElements[i].target;
    			}
    			theForm += this.renderFormField(formElements[i]);
    			break;
    		case 'form-submit' :
    			theForm += this.renderSubmitButton(formElements[i]);
    			break;
    		default:
    			;;; console.warn("[CH] Unknown field element for hda-generated-form: %o. This should not happen!", formElements[i].command );
    			break;
    		}
    	}
    	
    	theForm += '</form>';
    	
    	;;; console.debug("[CH] Generated form code: %o", theForm); 
    	
    	var whereToPlace = getElementByIdHDA('hda-form');
    	if (whereToPlace){
    		whereToPlace.innerHTML = theForm;
    	}
    	
    	// Focus on first form element
    	var focusable = getElementByIdHDA(firstFieldElement);
    	if (focusable){
    		focusable.focus();
    	}
    	
    },
    
    resetForm: function(){
    	var theForm = getElementByIdHDA('hda-form');
    	if (theForm){
    		theForm.innerHTML = "";
    	}
    },
    
    submitForm: function(){
    	var theQS = "";
    	var formElements = this.lastFormAction.children;
    	var target = null;

    	// Retrieving data from form and save it to the QS
    	for (var i = 0; i < formElements.length; i++){
    		switch (formElements[i].command){
    		case 'form-field':
    			var theField = getElementByIdHDA(formElements[i].target);
    			if (!theField){
    				;;; console.warn("[CH] Found a field that is not rendered: %o. This should not be happen.", formElements[i].target);
    			}else{
    				theQS += formElements[i].target + "=" + encodeURI(theField.value) + "&";
    			}
    			break;
    		case 'form-submit':
    			target = formElements[i].target;
    			break;
    		default:
    			;;; console.debug("[CH] Ignoring %o field", formElements[i].command);
    			break;
    		}
    	}
    	
    	theQS = theQS.substring(0, theQS.length-1);
    	
    	;;; console.debug("[CH] Ready to invoke node/rule %o with theese parameters: %o", target, theQS);
    	HDAInterface.sendFormEvent(target, theQS);
    	return true;
    },

    events : {
        /* Fired when someone clicks the TOGGLE button on the face player */
        faceVideoOn : new HDAEvent('faceVideoOn'),
        faceVideoOff : new HDAEvent('faceVideoOff'),

        /* Fired when someone clicks the REPEAT button on the face player */
        faceRepeat : new HDAEvent('faceRepeat'),

        /* Fired when someone clicks the LASTMENU button on the face player */
        faceLastMenu : new HDAEvent('faceLastMenu'),

        /* Fired when someone clicks the SKIP button on the face player */
        faceSkip : new HDAEvent('faceSkip'),

        faceHelp : new HDAEvent('faceHelp'),

        /* Fired when someone clicks a voice menu */
        menuChoice : new HDAEvent('menuChoice')
    },

    _onBeginSequence : function(node) {
    ;;; console.debug("[CH] begin sequence");
        this.resetMenu();
        this.resetForm();
        this.resetControls();
        this.clearAlternativeText();
        if (!this.textMode) {
            if(this.controls.skip) {
                HDAUtils.element.addClassName(this.controls.skip.element, 'active');
                HDAUtils.element.removeClassName(this.controls.skip.element,
                    'hover');
            }
        }
        if(this.controls.repeat) {
            HDAUtils.element.removeClassName(this.controls.repeat.element, 'active');
            HDAUtils.element.removeClassName(this.controls.repeat.element, 'hover');
        }
    },

    _onEndSequence : function() {
    ;;; console.debug("[CH] end sequence");
        if (!this.textMode) {
            if(this.controls.skip) {
                HDAUtils.element.removeClassName(this.controls.skip.element, 'active');
                HDAUtils.element.removeClassName(this.controls.skip.element, 'hover');
            }
            if(this.controls.repeat) {
                HDAUtils.element.addClassName(this.controls.repeat.element, 'active');
            }
        }
        if (this.controls.lastMenu){
        	if (this.lastMenuToRender){
        		this.lastMenuToRender = false;
                HDAUtils.element.addClassName(this.controls.lastMenu.element, 'active');
        	}
        }
    },

    _onDisplayMenu : function(menu) {
        this.displayMenu(menu);
    },

    _onVideoBegin : function(node, playMode) {
        this.resetForm();
        this.setCaption(node.caption);
        this.appendAlternativeText(node.alternativeText);
    },

    _onFaceLastMenu : function() {
        HDAUtils.element.removeClassName(this.controls.lastMenu.element,
                'active');
        HDAUtils.element.removeClassName(this.controls.lastMenu.element,
                'hover');
        HDAUtils.element
                .removeClassName(this.controls.repeat.element, 'active');
        HDAUtils.element.removeClassName(this.controls.skip.element, 'active');
    },

    _onMenuChoice : function(choice) {
        var item = HDAPlayer.currentMenu.children[choice];
        if (item.command != "open-url"
                || (item.command == "open-url" && item.params["target"] == "popup")) {
            this.resetMenu();
            if(this.controls.lastMenu)
            	this.lastMenuToRender = true;
    }
    ;;; console.log("[CH] Menu choice %o", item);
    },

    _onFaceVideoOff : function() {
        this.textMode = true;

        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.height = "1px";
        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.width = "1px";

        getElementByIdHDA('hda-alternativeText').style.display = "block";
        getElementByIdHDA("hda-controlVideoOff").style.display = "none";
        getElementByIdHDA("hda-controlVideoOn").style.display = "";
        if (this.player.currentNode)
            HDAFacePlayer.engine.stop();
        HDAFacePlayer.engine = HDADummyFacePlayer;
        if(this.controls.videoOn)
            HDAUtils.element.addClassName(this.controls.videoOn.element, 'active');
        if(this.controls.repeat)
            HDAUtils.element.removeClassName(this.controls.repeat.element, 'active');
        if(this.controls.skip)
            HDAUtils.element.removeClassName(this.controls.skip.element, 'active');
    },

    _onFaceVideoOn : function() {
        this.textMode = false;

        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.height = "";
        getElementByIdHDA(HDAFlashFacePlayer.flashTagId).style.width = "";

        getElementByIdHDA('hda-alternativeText').style.display = "none";
        getElementByIdHDA("hda-controlVideoOff").style.display = "";
        getElementByIdHDA("hda-controlVideoOn").style.display = "none";
        HDAFacePlayer.engine = HDAFlashFacePlayer;
        HDAUtils.element.addClassName(this.controls.videoOff.element, 'active');
        /*
         * Don't reactivate the repeat button here because it breaks when using
         * lastMenu
         */
        /*
         * if (this.player.currentNode)
         * HDAUtils.element.addClassName(this.controls.repeat.element,
         * 'active');
         */
    },

    _onFaceSkip : function() {
        HDAUtils.element.removeClassName(this.controls.skip.element, 'active');
    }

};
/**
 * Object that contains client log constants and client on/off switch
 * 
 */
var HDAClientLog = {
	traceClient: false,
	traceClientOnGA: false,
	
    HDA_CONNECTED: 1,
    HDA_ERROR: 2,
    HDA_FACE_HELP: 3,
    HDA_FACE_REPEAT: 4,
    HDA_FACE_SKIP: 5,
    HDA_FACE_VIDEO_OFF: 6,
    HDA_FACE_VIDEO_ON: 7,
    HDA_BANDWIDTH_LEVEL: 8
};var HDAFlashFacePlayer = {
    flashTagId: '_hda_flash_player',

    brainObject: null,
    
    hdaswfroot: "swf/",
    
    hdawidth: 160,
    
    hdaheight: 160,
    
    hdabgcolor: "ffffff",
    
    hdaextra: null,
    
    hdaclientlog: false,
    
    /**
     * Error Handler to override DWR default error handler
     */
    hdaErrorHandler: function(message, ex){
		if (message == null || message == ""){
			console.error("A server error has occured. Please check Brain logs");
		}else if (message.indexOf("0x80040111") != -1){
			// Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky 
		}else{
			console.error("HDA error detected. Object that describes the problem: %o", ex);
		}
    },
    
    /*
    dwr.engine._debug("Error: " + ex.name + ", " + ex.message, true);
    511 if (message == null || message == "") alert("A server error has occured.");
    512 // Ignore NS_ERROR_NOT_AVAILABLE if Mozilla is being narky
    513 else if (message.indexOf("0x80040111") != -1) dwr.engine._debug(message);
    514 else alert(message);
    */
    initFlashTag: function(swfroot, fms, res, extra, width, height, clientlog, brainScriptObject, service) {
		/*
		 * fms and res variable are useless in 3.0
		 * They are here only as placeholder for backward compatibility
		 */
	
        // Check if is it used an object to configure HDA and setup variables accordingly
        
        if ((swfroot != null) && (typeof(swfroot) == 'object')){
            ;;; console.debug('[FP] Configuring HDA using an object.');
            HDAFlashFacePlayer.hdaswfroot = swfroot.swfroot || 'swf/';
            HDAFlashFacePlayer.hdawidth = swfroot.width || 160;
            HDAFlashFacePlayer.hdaheight = swfroot.height || 160;
            HDAFlashFacePlayer.hdaextra = swfroot.extra || null;
            HDAFlashFacePlayer.hdabgcolor = swfroot.bgcolor || 'ffffff';
            if(HDAFlashFacePlayer.hdaextra == null) {
                HDAFlashFacePlayer.hdaextra = "bgcolor=" + HDAFlashFacePlayer.hdabgcolor;
            }else{
                HDAFlashFacePlayer.hdaextra += "&bgcolor=" + HDAFlashFacePlayer.hdabgcolor;
            }
            swfroot.brainScriptObject ? HDAFlashFacePlayer.brainObject = swfroot.brainScriptObject : HDAFlashFacePlayer.brainObject = HDABrain;
            ((swfroot.clientlog != null) && (swfroot.clientlog != undefined)) ? this.hdaclientlog = swfroot.clientlog : this.hdaclientlog = true;
            
            // WPL 121 - Add service Name
            HDAPlayer.service = swfroot.service;
        }else{
            ;;; console.debug('[FP] Configuring HDA using old method (positional).');
            HDAFlashFacePlayer.hdaswfroot = swfroot || 'swf/'; 
            HDAFlashFacePlayer.hdawidth = width || 160;
            HDAFlashFacePlayer.hdaheight = height || 160;
            HDAFlashFacePlayer.hdaextra = extra || null;
            HDAFlashFacePlayer.hdaclientlog = ((clientlog != null) && (clientlog != undefined)) ? clientlog : true;
            HDAFlashFacePlayer.brainObject = brainScriptObject || HDABrain;
            HDAPlayer.service = service;
        }
        
        // init DWR
        dwr.engine.setErrorHandler(this.hdaErrorHandler)
        dwr.engine.setWarningHandler(this.hdaErrorHandler)
        ;;; console.debug("[FP] Overridden DWR error and warning handler");
        
        // init player
        ;;; console.debug("[FP] Brain Object: " + HDAFlashFacePlayer.brainObject);
        HDAPlayer.brainObject = HDAFlashFacePlayer.brainObject;
        
        if (!this.hdaclientlog){
            HDAClientLog.traceClient = false;
            ;;; console.debug("[FP] Client trace disabled");
        }else{
            HDAClientLog.traceClient = true;
            ;;; console.debug("[FP] Client trace enabled");
        }
        var flashTag = new FlashTag(HDAFlashFacePlayer.hdaswfroot + "HDA.swf", HDAFlashFacePlayer.hdawidth, HDAFlashFacePlayer.hdaheight, HDAFlashFacePlayer.hdabgcolor);
        flashTag.setId(HDAFlashFacePlayer.flashTagId);
        var lcname = "hda-lc-" + new Date().getTime();
        var flashvars = "lcname=" + lcname;
        if (HDAFlashFacePlayer.hdaextra) {
            flashvars += "&" + HDAFlashFacePlayer.hdaextra;
        }
        flashTag.setFlashvars(flashvars);
        return flashTag;
    },

    init: function(swfroot, fms, res, extra, width, height, clientlog, brainScriptObject) {
		var flashTag = HDAFlashFacePlayer.initFlashTag(swfroot, fms, res, extra, width, height, clientlog, brainScriptObject);
		flashTag.write(document);
    },
    
    initFlashProxy: function(){ 
        /*
         * This empty function is here only for backward compatibility  
		 */
    },
    
    play: function(movie) {
        ;;; console.debug("[FP] play(" + movie + ")");
        this.flashProxy.playMovie(movie);
    },

    stop: function(movie) {
        ;;; console.debug("[FP] stop");
        this.flashProxy.stopMovie();
    },

    pause: function() {
        ;;; console.debug("[FP] pause");
        this.flashProxy.pauseMovie();
    },

    continuePlay: function() {
        ;;; console.debug("[FP] continue");
        this.flashProxy.pauseMovie();
    },
    
    videoOn: function() {
        ;;; console.debug("[FP] videoOn");
        this.flashProxy.videoOn();
    },
    
    videoOff: function() {
        ;;; console.debug("[FP] videoOff");
        this.flashProxy.videoOff();
    }
};

var HDADummyFacePlayer = {
    init: function() {
        ;;; console.debug("[DP] init");
    },

    play: function(movie) {
        ;;; console.debug("[DP] play(" + movie + ")");
        HDAFacePlayer.fireEvent("movieBegin");
        HDAFacePlayer.fireEvent("throwStartActions");
        HDAFacePlayer.fireEvent("throwEndActions");
        HDAFacePlayer.fireEvent("movieEnd");
    },

    stop: function(movie) {
        ;;; console.debug("[DP] stop");
    },

    pause: function() {
        ;;; console.debug("[DP] pause");
    },

    continuePlay: function() {
        ;;; console.debug("[DP] continue");
    },
    
    videoOn: function() {
        ;;; console.debug("[DP] videoOn");
        HDAFlashFacePlayer.videoOn();
    },
    
    videoOff: function() {
        ;;; console.debug("[DP] videoOff");
    }
};

var HDAFacePlayer = {
    engine: HDAFlashFacePlayer,
    hdaInterface: null, // reference to HDAInterface
    isExternalInterfaceInited: false,
    
    // Used to complete player initialization
    completeInit: function(){
		if (!HDAPlayer.isPlayerInited){
			HDAPlayer._setPlayerInited(true);
			this.findHDAInterface(top);
			this.hdaInterface.playerFinalization();
		}else{
			;;; console.debug('[FE] Player already init');
		}
	},

    fireEvent: function(event, params) {
        ;;; console.debug("[FE] event received: %o(%o)", event, params);
        if (!event) return;
        switch (event) {
          case "configuration":
          	return HDAPlayer._onConfiguration();
   			break;
          case "connected":
            HDAPlayer._onConnected(params);
            break;
          case "movieBegin":
            HDAPlayer._onVideoBegin(params);
            break;
          case "movieEnd":
            HDAPlayer._onVideoEnd(params);
            break;
          case "bandwidthLevel":
              HDAPlayer._setBandwidthLevel(params);
              break;
          case "throwStartActions":
              HDAPlayer._onThrowStartActions(params);
              break;
          case "throwEndActions":
              HDAPlayer._onThrowEndActions(params);
              break;
          case "error":
            HDAPlayer._onError(params);
            break;
          case "externalInterfaceReady":
			;;; console.debug("[FE] Received external interface ready signal "
				+ HDAPlayer);
			this.initExternalInterface();
          	break;
          case "playerReady":
          	;;; console.debug("[FE] Received player ready signal " + HDAPlayer);
          	this.completeInit();
          	break;
        }
        try {
          HDAFacePlayer.events[event].fire(params);
        } catch (e) {
          ;;; console.error("[FE] Unknown event received: '%s'", event);
        }
    },
    
    initExternalInterface : function() {
		if (!this.isExternalInterfaceInited) {
			this.isExternalInterfaceInited = true;
		}
		var ie = navigator.appName.indexOf("Microsoft") != -1;
		HDAFlashFacePlayer.flashProxy = window[HDAFlashFacePlayer.flashTagId] || document[HDAFlashFacePlayer.flashTagId];;;;
		console.debug('Init flashProxy: ');;;;
		console.debug(HDAFlashFacePlayer.flashProxy);
		_DHDA = HDAFlashFacePlayer.flashProxy;;;;
		console.debug('[FP] Flash player assigned to _DHDA: ' + _DHDA);
	},
    
    findHDAInterface: function(initialFrame){
    	if (this.hdaInterface != null){
    		return;
    	}
    	
		var interfaceFound = ((initialFrame.HDAInterface) && (initialFrame.HDAInterface.playerFrameName != null));
		if ((!interfaceFound) && (initialFrame.frames.length == 0)) {
			;;; console.debug("[FE] Interface not found in " + initialFrame.document.title + " and there are not child frames available");
			return;
		} else {
			if (interfaceFound) {
				;;; console.debug("[FE] Interface found in " + initialFrame.document.title);
				this.hdaInterface = initialFrame.HDAInterface;
				;;; console.debug("[FE] Interface object " + this.hdaInterface);
				return;
			} else {
				;;; console.debug("[FE] Interface not found in " + initialFrame.document.title + ". Looking on child frames.");
				for (var i = 0; i < initialFrame.frames.length; i++) {
					this.findHDAInterface(initialFrame.frames[i]);
				}
			}
		}
    },

    events: {
    	configuration:    			new HDAEvent('configuration'),
        connected:        			new HDAEvent('connected'),
        movieEnd:         			new HDAEvent('movieEnd'),
        throwStartActions:  		new HDAEvent('throwStartActions'),
        throwEndActions:  			new HDAEvent('throwEndActions'),
        moviePaused:      			new HDAEvent('moviePaused'),
        movieContinue:    			new HDAEvent('movieContinue'),
        movieSkipped:     			new HDAEvent('movieSkipped'),
        movieBegin:       			new HDAEvent('movieBegin'),
        bandwidthLevel:   			new HDAEvent('bandwidthLevel'),
        error:            			new HDAEvent('error'),
        externalInterfaceReady:		new HDAEvent('externalInterfaceReady'),
        playerReady:	  			new HDAEvent('playerReady')
    }
};

var _DHDA = null;
/**
 * Provides and interface to the player SDK (HDAPlayer).
 *
 * The player may reside in the same page or in another frame (from the same domain)
 * Upon loading of the page where this interface resides, <meta> informations
 * are read an followed to the player that, in turn, asks the server for the
 * relative node (clue node). Then a video is played and the node actions are performed
 */
var HDAInterface = {
   
   firstKey:null,
   firstParams:null,
   playerFrameName:null, 
   player: null,
 
   /**
   *	val can be: 
   *	- a String that rappresent the hdaFrame
   *	- a object like {key:"", params:"", frame:""}
   */
  start: function(val) {
  	if ((val != null) && (typeof(val) == 'object')){
  		;;; console.debug('[PL] HDAInterface.start with a Object.');
    	if (val.key) this.firstKey = val.key;
    	if (val.params) this.firstParams = val.params;
    	if (val.frame) {
    		this.playerFrameName = val.frame;
    	}else{
	    	this.playerFrameName = 'hda';
    	}
  	}else{
	    if (!val) {
	    	this.playerFrameName = 'hda';
	    }else{
	    	this.playerFrameName = val;
	    }
	}
  	
    this.findHDA(top);
    if (this.player === null){
            console.debug("[PL] Player not found. Assuming it is in init fase.");
            return; 
    }
    if(this.player._isPlayerInited()){
            console.debug("[PL] Player has been already initialized. Send meta...");
            this.sendMeta(); 
    }else{
            console.debug("[PL] Player found but not init yet.");
            this.player = null;
    }
  },
  
  /**
   * This is to be used by some "onclick" event handlers that could reside
   * in the "main" page alongside (for example) an A element
   */
  sendEvent: function(key, params) {
    this.player.sendEvent(key, params, "interaction point");
  },
  
  /**
   * Internal function used with forms, if available
   */
  sendFormEvent: function(key, params){
	this.player.sendEvent(key, params, "form");  
  },

  /**
   * Reads specific <meta> from the HEAD element and feed them to 
   * the sendEvent of the player, thus starting the player itself.
   */
  sendMeta: function() {
    var meta = document.getElementsByTagName('meta');
    var res = new Object();
    for (var i = 0; i < meta.length; i++) {
      if (meta[i].name != null && meta[i].name.indexOf('hda-') == 0) {
        var attribute = meta[i].name.substring(4, meta[i].name.length);
        res[attribute] = meta[i].content;
      }
    }
    if (!!res.key) {
      this.player.sendEvent(res.key, res.value, "start");
    }
  },
  
	findHDA : function(initialFrame) {
		if (this.player){
			return;
		}
		var playerFound = initialFrame.document.getElementById('hda-playerHda');
		if ((!playerFound) && (initialFrame.frames.length == 0)) {
			;;; console.debug("[PL] Player not found in " + initialFrame.document.title + " and there are not child frames available");
			return;
		} else {
			if (playerFound) {
				;;; console.debug("[PL] Player found in " + initialFrame.document.title);
				this.player = initialFrame.HDAPlayer;
				;;; console.debug("[PL] Player object " + this.player);
				return;
			} else {
				;;; console.debug("[PL] Player not found in " + initialFrame.document.title + ". Looking on child frames.");
				for (var i = 0; i < initialFrame.frames.length; i++) {
					this.findHDA(initialFrame.frames[i]);
				}
			}
		}
	},  
  
  
  /**
   * Find the Player into page DOM (even if annidated in frames)
   * and initialize the player itself, eventually sending metadata
   * 
   * This function is invoked by the FlashPlayer when it starts to play idle
   */
  playerFinalization: function(){
    this._to = setInterval((function() {
      if (!this._loops) {
        this._loops = 1;
      }
      ;;; console.debug("[PL] Waiting for the HDA player initialisation... Number of loops: " + this._loops);
      this._loops++;
      if (this._loops > 100) {
        /* OK, stop searching the player */
        clearInterval(this._to);
        ;;; console.error("[PL] Failed finding player");
        return;
      }

      this.findHDA(top);
      
      if (this.player){
        this.player.init();
        if (this.player.configError == null){ // No error on init!	
        	if(this.firstKey != null){
        		;;;console.debug("[PL] Send first request : key:"+this.firstKey+" params:"+this.firstParams);
        		this.player.sendEvent(this.firstKey, this.firstParams, "start");
        	}else{
        		;;;console.debug("[PL] Send Meta");
        		this.sendMeta();
        	}
        }

        clearInterval(this._to);
        ;;; console.debug("[PL] Player Finalized!");
        // If in text mode, switch the mode...
        if (this.player.currentBandwidthLevel == 'text'){
			this.player.chrome.appendErrorMessage("hda-noVideoErrorMessage");
			this.player.chrome._onFaceVideoOff();
        }
        // If an error has occurred
        if (this.player.configError != null){
        	this.player.chrome.appendErrorMessage("hda-noVideoErrorMessage");
        	this.player.chrome._onFaceVideoOff();
			console.error(this.player.configError)
        }
        return;
      }
      
    }).bind(this), 100);
  }

};
/**
 * Provides an interface to
 * - Brain (send node requests, receives nodes, via DWR)
 * - The SWF player (via the SWF player API interface)
 * - The "Customized Environment", via dispatching of custom events
 */
var HDAPlayer = {

	/**
	 * Constant used to set and retrieve player status from cookies
	 */
	HDA_PLAYER_STATUS : 'hda-player-status',
	
	service: null, // Service associated with this player instance

	configError: null, // If a configuration error has been detected, this contains the error message
	brainObject : null,
	isVideoActive : true,
	isRenderingError : false,
	currentNode : null,
	currentMenu : null,
	referrerAction : null,
	protocol : "http",
	audioOnly : false,
	connected : false,
	enabled : true,
	endSequence : true,
	beginSequence : true,
	sequenceAborted : false,
	currentBandwidthLevel : 'audio-video',
	nodeBuffer : new HDABuffer(),
	lastMenu : null,
	isInternalRequest : false,
	isPlayerInited : false,
	/* playMode is 'normal' or 'repeat' or 'skip' */
	playMode : 'normal',
	chrome : HDAChrome,
	firstInit : true, // If the player is initializing
	autoTextMode: false, // True if text mode is switched by the player
	skipMode: false, // If the skip mode has been selected (used to switch on dummy player on end movie

	/**
	 * Start up the player environment and shows the swf player.
	 * Called by the interface
	 */
	init : function() {
		this.chrome = HDAChrome;
		this.endSequence = true;
		this.beginSequence = true;
		this.sequenceAborted = false;
		this.lastMenu = null;
		this.isInternalRequest = false;
		this.playMode = 'normal';

		this.nodeBuffer = new HDABuffer();

		// If player is already initialized (back browser button),
		// just cold reset the chrome and return. We test the menuButtons
		if (this.chrome.menuButtons) {
			this.chrome.reset(true);
			this.events.playerReady.fire();
			return;
		}

		this.chrome.init(this);

		/* Subscribe to Face player generated events */
		//HDAFacePlayer.events.movieEnd.subscribe(this._onVideoEnd, this);
		//HDAFacePlayer.events.movieBegin.subscribe(this._onVideoBegin, this);
		//HDAFacePlayer.events.error.subscribe(this._onError, this);
		/* Subscribe to Chrome generated events */
		//this.chrome.events.faceVideoOn.subscribe(this._onFaceVideoOn, this);
		//this.chrome.events.faceVideoOff.subscribe(this._onFaceVideoOff, this);
		//this.chrome.events.faceSkip.subscribe(this._onFaceSkip, this);
		//this.chrome.events.faceRepeat.subscribe(this._onFaceRepeat, this);
		//this.chrome.events.faceLastMenu.subscribe(this._onFaceLastMenu, this);
		//this.chrome.events.menuChoice.subscribe(this._onMenuChoice, this);
		//this.chrome.events.faceHelp.subscribe(this._onFaceHelp, this);
		/* Inform them all: we are ready */
		this.events.playerReady.fire();
	},

	/**
	 * Set of custom events a client can subscribe to
	 */
	events : {

		/* Fired when sfw is initialized */
		configuration : new HDAEvent('configuration'),

		/* Fired when a menu have to be displayed */
		displayMenu : new HDAEvent('displayMenu'),
		
		/* Fired when a form have to be rendered */
		renderForm : new HDAEvent('renderForm'),

		/* Fired when a node have to be loaded */
		loadNode : new HDAEvent('loadNode'),

		/* Fired when a rule have to be loaded */
		loadRule : new HDAEvent('loadRule'),

		/* Fired when a URL have to be loaded */
		loadURL : new HDAEvent('loadURL'),

		/* Fired when the face player notify a videoBegin event */
		videoBegin : new HDAEvent('videoBegin'),

		/* Fired when the face player notify a videoEnd event */
		videoEnd : new HDAEvent('videoEnd'),

		/* Fired by the chrome when someone clicks the TOGGLE button on the face player */
		faceVideoOn : new HDAEvent('faceVideoOn'),
		faceVideoOff : new HDAEvent('faceVideoOff'),

		/* Fired by the chrome when someone clicks the SKIP button on the face player */
		faceSkip : new HDAEvent('faceSkip'),

		/* Fired by the chrome when someone clicks the REPEAT button on the face player */
		faceRepeat : new HDAEvent('faceRepeat'),

		/* Fired by the chrome when someone clicks the LAST MENU button on the face player */
		faceLastMenu : new HDAEvent('faceLastMenu'),

		faceHelp : new HDAEvent('faceHelp'),

		/* Fired by the chrome when someone clicks a menu voice */
		menuChoice : new HDAEvent('menuChoice'),

		/* Fired when the this.init routines has terminated */
		playerReady : new HDAEvent('playerReady'),

		/* Fired when a node has arrived from Brain */
		nodeLoaded : new HDAEvent('nodeLoaded'),

		/* Fired when a sequence begins */
		beginSequence : new HDAEvent('beginSequence'),

		/* Fired when a sequence terminates */
		endSequence : new HDAEvent('endSequence')
	},

	/**
	 * Issue an event request to Brain
	 * @params {Object} Key and Params
	 */
	sendEvent : function(key, params, source) {
		if ((source === null) || (source === 'undefined')){
			source = null;
			;;; console.debug("[PL] Source not defined in sendEvent.");
		}
		if (!this.connected) {
			var _loops = 0;
			var _to = setInterval((function() {
				_loops++;
				if (_loops > 100) {
					clearInterval(_to);
					console.error(
							"[PL] Connection failed, can't send event %s", key);
					if (_loops == 101) {
						HDAFacePlayer.fireEvent("error", "connection-failed");
					}
					return;
				}
				if (this.connected) {
					clearInterval(_to);
					this.sendEvent(key, params);
				}
			}).bind(this), 100);
			return;
		}
		this.playMode = 'normal';
		var hdaParams = "hdaAudioOnly=" + this.audioOnly
				+ "&hdaStreamingProtocol=" + this.protocol;

		if (!params || params == 'undefined') {
			params = hdaParams;
		} else {
			if (params instanceof String) {
				params += ('&' + hdaParams);
			} else if (typeof params === 'object') {
				var qs = '';
				if (params['action_attributes']) {
					qs = params['action_attributes'];
					delete params['action_attributes'];
					if (qs.charAt(qs.length) != '&')
						qs = qs + '&';
				}

				for (var p in params) {
					qs = qs + p + '=' + params[p] + '&';
				}

				if (qs === '') {
					qs = '&'
				}

				params = qs + hdaParams;
			}
		};;;
		console.debug('[PL] Sending event: key=%o params=%o source=%o', key, params, source);
		this.brainObject.sendEvent({
					key : key,
					referrerActionId : this.referrerAction
							? this.referrerAction.actionId
							: null,
					params : params,
					source : source,
					serviceId : this.service
				}, this._processResponse.bind(this));
		this.isInternalRequest = false;
		this.referrerAction = null;
	},

	sendInternalEvent : function(key, params) {
		this.sendEvent(key, params);
		this.isInternalRequest = true;
	},

	disable : function() {
		this._abortSequence();
		this.enabled = false;
	},

	enable : function() {
		this.enabled = true;
	},

	_abortSequence : function() {
		HDAFacePlayer.engine.stop();
		this.sequenceAborted = true;
		this.endSequence = true;
		this.beginSequence = true;
	},

	/*  P R I V A T E methods */

	/**
	 * Processes a Brain response (callback for the sendEvent)
	 * This is to be considered the "entry point" for every new request/load
	 * @params {Object} A node
	 */
	_processResponse : function(response) {
		if (!this.enabled) {
			return;
		}
		if (!response) {
			;;;
			console.debug('[PL] Null response');
			this._onError('null-response');
			return;
		};;;
		console.debug('[PL] Response received. Now playing ' + response.movie);

		if ((response.movie == null) && (this.isVideoActive)) {
			;;;
			console
					.debug('[PL] Detected a Rendering Error. Prepare to switch in text mode.');
			this.isRenderingError = true;
		} else {
			if ((this.isRenderingError) && (this.isVideoActive)) {
				;;;
				console
						.debug('[PL] Restoring state after a previous Rendering Error.');
				this.isRenderingError = false;
				this.chrome._onFaceVideoOn();
			}
		}

		this.currentNode = response;
		if (!this.isInternalRequest) {
			;;;
			console
					.debug('[PL] Last request was not from a menu, resetting lastMenu');
			this.lastMenu = null;
			this._onFaceLastMenu();
			this.chrome.events.faceLastMenu.fire();
			this.nodeBuffer = new HDABuffer();
		}
		/* CurrentMenu is reset here and will be eventually set when
		 * handling a displayMenu event */
		this.currentMenu = null;

		this._detectSequenceBounds();

		/* If a sequence has previously ended (or this is the very first frame), empty the movie buffer */
		this.sequenceAborted = false;
		if (this.beginSequence) {
			;;;
			console.debug("[PL] begin sequence");

			if (this.isRenderingError) {
				;;;
				console
						.debug('[PL] Switching to text mode due to a rendering error.');
				this.chrome._onFaceVideoOff();
			}

			this.nodeBuffer = new HDABuffer();
			this.chrome._onBeginSequence(this.currentNode);
			this.events.beginSequence.fire(this.currentNode);
		}

		this.nodeBuffer.store(this.currentNode);
		this._playMovie(response.movie);
		this.events.nodeLoaded.fire(this.currentNode);

		if (this.endSequence) {
			;;;
			console.debug("[PL] end sequence");
		}

	},

	_detectSequenceBounds : function() {
		this.beginSequence = this.endSequence;
		this.endSequence = false;
		if (this.currentNode && this.currentNode.actions) {
			for (var i = 0; i < this.currentNode.actions.length; i++) {
				switch (this.currentNode.actions[i].command) {
					case 'open-url' :
						this.endSequence = true;
						break;
					case 'menu' :
						this.endSequence = true;
						break;
					case 'form' :
						this.endSequence = true;
						break;
				}
			}
		} else {
			this.endSequence = true;
		}
	},

	_playMovie : function(movie) {
		HDAFacePlayer.engine.play(movie);
	},

	_skipMovie : function() {
		HDAFacePlayer.engine.stop();
	},

	/**
	 * Event handlers for the chrome generated events
	 */

	_onConfiguration : function() {
		
		if (this.configError != null){
			return null;
		}
		
		if ((this.service === null) || (this.service === undefined)){
			if (this.configError === null){
				this.configError = "Unable to load player configuration. Required parameter \"service\" is unavailable. Please check your configuration.";
				HDAFacePlayer.completeInit();
				return null;
			}
		}

		;;; console.debug("[PL] swf want configuration ");
		var result = null;
		var cb = {
			async : false,
			callback : function(config) {
				;;;
				console.debug("[PL] configuration received");
				result = config;
				return config;
			}
		};
	    this.brainObject.getPlayerConfiguration(this.service, cb);
		  ;;; console.info("[PL] get configuration to swf: %o", result);

		if (!result) {
			if (this.configError === null){
				this.configError = "Unable to load player configuration. System is not initialized and it is not possible to get videos.";
				HDAFacePlayer.completeInit();
				return null;
			}
		}

		return result;
	},

	/**
	 * This is called when the user clicks on a menu item
	 * @params {Integer} The selected menu voice index in the current node action
	 */
	_onMenuChoice : function(choice) {
		;;;
		console.debug("[PL] Menu item %o selected",
				this.currentMenu.children[choice]);

		this._runActionOnMovieEnd(this.currentMenu.children[choice], true);

		/* Once a menu voice is selected, the menu is wiped out. So, we need
		   save the current menu as the "lastMenu" to be able to ri-present it */
		this.lastMenu = {
			menu : this.currentMenu,
			alternativeTextHTML : this.chrome.getAlternativeTextHTML(),
			caption : this.chrome.getCaption()
		};

		/* Forward event */
		this.events.menuChoice.fire(choice);
	},

	_onFaceVideoOn : function() {
		console.debug("[PL] _onFaceVideoOn invoked. AutoTextMode value -> " + this.autoTextMode);
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_FACE_VIDEO_ON);
		};
		;;;console.debug("[PL] Setting " + this.HDA_PLAYER_STATUS
				+ " to on using cookie");
		HDAUtils.cookie.write(this.HDA_PLAYER_STATUS, true, 30);
		if (this.autoTextMode){
			;;; console.debug("[PL] Video ON after an error. Reinit the player");
			this.currentBandwidthLevel = "audio-video";
			this.autoTextMode = false;
			HDAFlashFacePlayer.flashProxy.reinitPlayer();
			return;
		}
		;;;console.debug("[PL] Restart Idle video");
		HDAFacePlayer.engine.videoOn();
		;;;console.debug("[PL] Video ON");
		/* Forward event */
		this.events.faceVideoOn.fire();
		this.isVideoActive = true;
	},

	_onFaceVideoOff : function() {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_FACE_VIDEO_OFF);
		};;;
		console.debug("[PL] Setting " + this.HDA_PLAYER_STATUS
				+ " to off using cookie");
		HDAUtils.cookie.write(this.HDA_PLAYER_STATUS, false, 30);;;;
		console.debug("[PL] Freeze Idle video");
		HDAFacePlayer.engine.videoOff();
		;;; console.debug("[PL] Video OFF");
		/* Forward event */
		this.events.faceVideoOff.fire();
		this.isVideoActive = false;
	},

	_onFaceSkip : function() {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_FACE_SKIP);
		};;;
		console.debug("[PL] Skip");
		
		this.skipMode = true;

		/* Forward event */
		this.events.faceSkip.fire();

		/* This will issue a "stop" to the face, and a videoEnd event
		 * will then be submitted by the face */
		this._skipMovie();
		/* The Flash engine is restored in the player's endSequence event 
		console.debug("[PL] Using Dummy Player for skip sequence");
		HDAFacePlayer.engine = HDADummyFacePlayer;
		*/
	},

	_onFaceLastMenu : function() {
		;;;
		console.debug("[PL] Last menu");

		if (this.lastMenu) {
			this.currentMenu = this.lastMenu.menu;
			this.chrome._onDisplayMenu(this.lastMenu.menu);
			this.events.displayMenu.fire(this.lastMenu.menu);
			/* FIMXE: This should be in the chrome */
			this.chrome
					.setAlternativeTextHTML(this.lastMenu.alternativeTextHTML);
			this.chrome.setCaption(this.lastMenu.caption);

			this._abortSequence();
		}
		/* Forward event */
		this.events.faceLastMenu.fire();
	},

	_onFaceRepeat : function() {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_FACE_REPEAT);
		};;;
		console.debug("[PL] Repeat");
		this.nodeBuffer.rewind();
		this.playMode = 'repeat';
		// Begin re-playing the nodeBuffer (next movie will be issued on movieEnd event)

		this.chrome._onBeginSequence();

		// Show internal menu
		/*
		if (this.currentMenu) {
			this.chrome.displayMenu(this.currentMenu);
		}
		*/
		
		this.currentNode = this.nodeBuffer.fetch();
		if (this.currentNode) {
			this._playMovie(this.currentNode.movie);
		}

		/* Forward event */
		this.events.beginSequence.fire(this.currentNode);
		this.events.faceRepeat.fire();
	},

	_onFaceHelp : function() {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_FACE_HELP);
		}
		this.events.faceHelp.fire();
	},

	_onConnected : function(protocol) {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_CONNECTED,
					"protocol=" + protocol + ";");
		}
		this.protocol = protocol;
		this.connected = true;
		if (HDAUtils.cookie.read(this.HDA_PLAYER_STATUS) == 'false') {
			;;;
			console
					.debug("[PL] Player previously disabled by user. Disabling again");
			this.chrome._onFaceVideoOff();
			this._onFaceVideoOff();
		}
	},

	_setBandwidthLevel : function(level) {
		if (this.currentBandwidthLevel == level) {
			console.debug("[PL] Not changing the bandwidth level. It is already at level \"" + level + "\"");
			return;
		}
		this.currentBandwidthLevel = level;
		;;;console.debug('[PL] Setting bandwidth to level %o', level);
		
		if (level == "text") {
			this.autoTextMode = true;
			this.audioOnly = false;
			if (!this.isPlayerInited){
				this.connected = true;
				HDAFacePlayer.fireEvent("playerReady");
				return;
			}
			HDAChrome.appendErrorMessage("hda-noVideoErrorMessage");
			HDAChrome._onFaceVideoOff();
		} else if (level == "audio") {
			this.audioOnly = true;
		} else if (level == "audio-video") {
			this.audioOnly = false;;;;
			console.debug("New level set: audioOnly -> " + this.audioOnly);
			HDAChrome._onFaceVideoOn();
		}

		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_BANDWIDTH_LEVEL,
					"level=" + level + ";");
		};
	},

	/* Set the Player inited status */
	_setPlayerInited : function(isInited) {
		this.isPlayerInited = isInited;
	},

	_isPlayerInited : function() {
		return this.isPlayerInited;
	},
	
	_resetAutoTextMode: function() {
		this.autoTextMode = false;
	},

	/* Handlers for the player generated events */
	_onVideoBegin : function() {
		;;;
		console.debug('[PL] Player informs us that movie has BEGAN ('
				+ this.playMode + ')');

		this._detectSequenceBounds();
		this.chrome._onVideoBegin(this.currentNode, this.playMode);
		this.events.videoBegin.fire(this.currentNode, this.playMode);
		if (this.playMode == 'repeat') {
			/* No further processing if we are just repeating movies */
			return;
		}
	},
	
	_onThrowStartActions: function(){
		this._runActions(0, true, false);
	},

	_onThrowEndActions: function(){
		var openUrls = true;
		/* If we are repeating movies, just start the next one */
		if (this.playMode == 'repeat') {
			openUrls = false;
		}

		this._runActions(-1, openUrls, false);
	},
	
	_onVideoEnd : function() {
		;;;
		console.debug('[PL] Player informs us that movie has ENDED');
		if (this.sequenceAborted) {
			;;;
			console.debug('[PL] Sequence aborted');
			return;
		}
		
		/* Eventually inform the client and the chrome */
		this.events.videoEnd.fire(this.currentNode, this.playMode);

		if (this.skipMode === true){
			;;; console.debug("[PL] Skip mode activated, using Dummy player");
			HDAFacePlayer.engine = HDADummyFacePlayer;
		}

		
		var openUrls = true;
		/* If we are repeating movies, just start the next one */
		if (this.playMode == 'repeat') {
			openUrls = false;
			this.currentNode = this.nodeBuffer.fetch();
			if (this.currentNode) {
				this._playMovie(this.currentNode.movie);
				return;
			} else {
				/* Repeat buffer ended */
				this.playMode = 'normal';
				this.currentNode = this.nodeBuffer.latest();
				this.chrome._onEndSequence();
			}
		}

		if (this.endSequence) {
			this.chrome._onEndSequence();
			this.events.endSequence.fire();
			if (this.skipMode === true){
				;;; console.debug("[PL] End sequence detected. Skip mode active -> Diabling Skip Mode");
				HDAFacePlayer.engine = HDAFlashFacePlayer;
				this.skipMode = false;
			}
		}
		
		this._runActions(-1, openUrls, true);
	},

	_runActions : function(when, openUrls, movieEnd) {
		if (this.currentNode && this.currentNode.actions) {
			for (var i = 0; i < this.currentNode.actions.length; i++) {
				if (this.currentNode.actions[i].runAt == when) {
					if (movieEnd === false){
						this._runAction(this.currentNode.actions[i], openUrls);
					}else{
						this._runActionOnMovieEnd(this.currentNode.actions[i], openUrls);
					}
				}
			}
		}
	},
	
	_runActionOnMovieEnd: function(action, openUrls) {
		switch (action.command) {
        case 'open-url' :
            if (openUrls) {
                    // We build a structured object for the convenience of the client
                    var url = {
                            method : action.params["method"]
                                            ? action.params["method"]
                                            : "GET",
                            url : action.target,
                            target : action.params["where"],
                            popup : {
                                    width : action.params["width"],
                                    height : action.params["height"]
                            }
                    };
                    // The actual loading of the URL is delegated to the client
                    this.events.loadURL.fire(url);
            }
            break;
		case 'load-item' :
			this.referrerAction = action;
			this.sendInternalEvent(action.target, action.params);
			this.events.loadNode.fire(this.currentNode, this.playMode);
			break;
		default :
			if (HDAClient.runCustomAction) {
				HDAClient.runCustomAction(action);
			}
			break;
		}
	},

	_runAction : function(action, openUrls) {
		this.referrerAction = action;
		switch (action.command) {
			/*
			case 'load-item' :
				this.sendInternalEvent(action.target, action.params);
				this.events.loadNode.fire(this.currentNode, this.playMode);
				break;
			*/
			case 'open-url' :
				if (openUrls) {
					if (action.runAt == 0){ // WPL-123 Not double the openUrl
						// We build a structured object for the convenience of the client
						var url = {
								method : action.params["method"]
								         ? action.params["method"]
								         : "GET",
							    url : action.target,
						        target : action.params["where"],
						        popup : {
							    width : action.params["width"],
							   height : action.params["height"]
						    }
					    };
						// The actual loading of the URL is delegated to the client
						this.events.loadURL.fire(url);
					};
				}
				break;
			case 'client-command' :
				// TODO
				break;
			case 'menu' :
				this.currentMenu = action;
				this.chrome.displayMenu(action);
				this.events.displayMenu.fire(action);
				break;
			case 'form' :
				this.chrome.renderForm(action);
				this.events.renderForm.fire(action);
			/*	
			default :
				if (HDAClient.runCustomAction) {
					HDAClient.runCustomAction(action);
				}
				break;
			*/
		}
	},

	_onError : function(error, message) {
		if (HDAClientLog.traceClient) {
			this.brainObject.traceClient(this.service, HDAClientLog.HDA_ERROR, "error="
							+ error + ";message=" + message + ";");
		}

		// Handling connection-failed case
		// Init of player should proceed anyway
		if ((error == 'connection-failed') && (HDAPlayer.firstInit)) {
			HDAPlayer.firstInit = false;
			HDAFacePlayer.fireEvent("playerReady");
		} else {
			this.autoTextMode = true;
			this.chrome.appendErrorMessage(error, message);
			this.chrome._onFaceVideoOff();
			this.chrome.events.faceVideoOff.fire();
		}

		;;;
		console.debug('[PL] received error: ' + error);
	}
};
/**
 * A simple FIFO buffer with an internal status and a rewind
 */
function HDABuffer() {
  this.items = new Array();
  this.next = 0;
};

HDABuffer.prototype = {
  /**
   * Fetches the next item from the buffer. null if EOB 
   */
  fetch: function() {
    if (this.next >= this.items.length)
      return null;
    return this.items[this.next++];
  },
  
  /**
   * Adds an item at the buffer tail
   * @params {any} The item to be pushed in the buffer
   */
  store: function(item) {
    this.items.push(item);
  },
  
  /**
   * Rewinds the buffer pointer (next fetch will get the first item) 
   */
  rewind: function() {
    this.next = 0;
  },
  
  /**
   * Returns the latest fetched item or the first one
   */
  latest: function() {
    if (this.next > 0)
      return this.items[this.next - 1];
    return null;
  }
};

var HDAUtils = {
  /**
   * Provides cross browser HTML element handling
   */
 element: {
    hasClassName: function(element, className) {
      element = getElementByIdHDA(element);
      return !!element.className.match(new RegExp("\\b"+className+"\\b"));
    },
    
    addClassName: function(element, className) {
      element = getElementByIdHDA(element);
      if (!this.hasClassName(element, className)) element.className = (element.className+' '+className);
    },
    
    removeClassName: function(element, className) {
      element = getElementByIdHDA(element);
      if (this.hasClassName(element, className)) element.className = element.className.replace(className, '');
    }
  },

  /**
   * Provides cross browser event handling routines
   */
  DOMEvent: {
    /**
     * Set an observer (handler) for an event
     * @param {String} The element to attach event to (id or object)
     * @param {String} The name of the event to observe
     * @param {Function} The handler for the event
     */
    observe: function(element, event, observer) {
      element=getElementByIdHDA(element);
      if (event == 'keypress' &&
          (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent))
        event = 'keydown';
      if (element.addEventListener) {
        element.addEventListener(event, observer, true);
      } else if (element.attachEvent) {
        element.attachEvent('on' + event, observer);
      }
    },
    
    getTarget: function(evt) {
      if (!evt)
        evt = window.event;
      var node = evt.target || evt.srcElement;
      if (node && node.nodeName &&
              "#TEXT" == node.nodeName.toUpperCase()) {
          return node.parentNode;
      } else {
          return node;
      }
    }
  },
  
  /**
   * Provides cookie management methods
   */
  cookie: {
    /**
     * Set a cookie
     * @method write
     * @param {String} Name of the cookie
     * @param {String} Value for the cookie
     * @param {String} Number of days for it to expire (optional)
     */
    write: function(name,value,days) {
      if (days) {
        var date = new Date();
        date.setTime(date.getTime()+(days*24*60*60*1000));
        var expires = "; expires="+date.toGMTString();
      }
      else var expires = "";
      document.cookie = name+"="+value+expires+"; path=/";
    },
    /**
     * Read a cookie
     * @method read
     * @param {String} Name of the cookie
     * @return {String} the actual value of the cookie
     */
    read: function(name) {
      var nameEQ = name + "=";
      var ca = document.cookie.split(';');
      for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
      }
      return null;
    },
    /**
     * Erase a cookie
     * @method erase
     * @param {String} Name of the cookie
     */
    erase: function(name) {
      this.write(name,"",-1);
    }    
  },

  /**
   * Copy an object by value
   * js copies object by reference; we'll sometimes need a copy by value 
   * @param {Object} The object to be copied
   * @return {Object} A new copy of the object
   */
  copyObject: function(o) {
    if(typeof(o) != 'object') return o;
    if(o == null) return o;
    var c = new Object();
    for (var e in o)
      c[e] = o[e];
    return c;
  },
  
  /* Very simple camelization: ToggleVideo -> toggleVideo */
  camelize: function(str) {
    return str.charAt(0).toLowerCase() + str.substring(1);
  }
};

if (!Function.prototype.bind) {
  Function.prototype.bind = function(object) {
    var __method = this;
    return function() {
      return __method.apply(object, arguments);
    };
  };
}

/**
 * Provides a shortcut for document.getElementById()
 * @param {String} The DOM id to retrieve
 */
function getElementByIdHDA()
{
    id=arguments[0];
    if (typeof id == "string")
      element = document.getElementById(id);
    else
      element = id;
    return element;
}

/*
if (typeof $ == "undefined") {
  window.$ = function(id) {     
    if (typeof id == "string")
      element = document.getElementById(id);
    else
      element = id;
    return element;
  };
}
*/

function HDAEvent(name) {
  this.name = name;
  this.subscribers = new Array();
  this.subscribe = function(fn, scope) {
    // Do not double-subscribe
    for (var i=0; i < this.subscribers.length; i++) {
      if (this.subscribers[i].fn == fn) {
        return;
      }
    }
    this.subscribers.push({
      fn: fn,
      scope: scope
    });
  };
  this.unsubscribe = function(fn, scope) {
    // Do not double-subscribe
    for (var i=0; i < this.subscribers.length; i++) {
      if (this.subscribers[i].fn == fn) {
        this.subscribers.splice(i, 1);
        return;
      }
    }
  };
  this.fire = function() {
    var args = [];
    for (var i=0; i < arguments.length; ++i) {
      args.push(arguments[i]);
    }
    for (i=0; i < this.subscribers.length; i++) {
      this.subscribers[i].fn.apply(this.subscribers[i].scope, args);
    }
  };
};

/** Prevent errors when Firebug is not enabled */
if (!("console" in window) || !("firebug" in console)) {
    var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
    "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];

    window.console = {};
    for (var i = 0; i < names.length; ++i)
        window.console[names[i]] = function() {};
}
