// ******************************************************************************
// ** NABUTALK.JS                                                              **
// ******************************************************************************
// ** product: NabuTalk AJAX API                                               **
// **    file: nabutalk.js                                                     **
// ** authors: Sylvain Camus, Vincent Louis, Thierry Martinez, Franck Panaget  **
// ** version:                                                                 **
// **    date:                                                                 **
// **  status: restricted                                                      **
// ******************************************************************************
// ** This file is the property of Dialonics. It may be provided only under    **
// ** license. If you do not have received a notice with this file, or if you  **
// ** disagree with the terms and conditions of this notice, you must not use, **
// ** copy or communicate this file to third-parties, and you must destroy any **
// ** copy of this file in your possession.                                    **
// **                                                                          **
// ** Copyright (C) 2008-2011, Dialonics - All rights reserved                 **
// ** www.dialonics.com - license@dialonics.com                                **
// ******************************************************************************

/* namespace */ var NabuTalk = {
	TITLE   : "NabuTalk AJAX API",
	COMPANY : "Dialonics"
};

////////////////////////////////////////////////////////////////////////////////

/*************
 * UTILITIES *
 *************/

/* ============== *
 * ALERTS / DEBUG *
 * ============== */

/* public static */ NabuTalk.Alert = (function()
{
	var ok = true;

	return function(type, message)
	{
		if ( ok ) {
			var message = NabuTalk.TITLE+" by "+NabuTalk.COMPANY+"\n\n\n"+type+" !\n\n"+message+"\n\n\n";
			message += "Do you want to continue to show messages?";
			ok = confirm(message);
		}
	}
})();

/* public static */ NabuTalk.Error = function(message)
{
	NabuTalk.Alert("ERROR", message);
};

/* public static */ NabuTalk.Exception = function(what, e)
{
	NabuTalk.Alert("EXCEPTION", "in "+what+":\n\n"+e.name+": "+e.message);
};

/* public static */ NabuTalk.Info = function(message)
{
	NabuTalk.Alert("INFORMATION", message);
};

/* public static */ NabuTalk.NotSupported = function(what)
{
	NabuTalk.Alert("NOT SUPPORTED", what+": not supported by your browser!");
};

/* public static */ NabuTalk.Debug = function(message)
{
	message = NabuTalk.TITLE+": "+message;
	window.status = message;
	document.title = message;
};

/* ====== *
 * DOLLAR *
 * ====== */

/* public static */ NabuTalk.$ = function()
{
	var elements = new Array();
	var n = arguments.length;
	for ( var i = 0; i < n; ++i ) {
		var element = arguments[i];
		if ( typeof element == "string" ) element = document.getElementById(element);
		if ( arguments.length == 1 ) return element;
		elements.push(element);
	}
	return elements;
};

/* ============== *
 * XML PROCESSING *
 * ============== */

/* public static */ NabuTalk.ReplaceXMLEntities = function(text)
{
	var replace = [["&", "38"], ['"', "34"], ["'", "39"], ["<", "60"], [">", "62"]];
	var n = replace.length;
	for ( var i = 0; i < n; ++i ) {
		var pair = replace[i];
		text = text.replace(new RegExp(pair[0], "g"), "&#"+pair[1]+";");
	}
	return text;
};

/* public static */ NabuTalk.surrounding = "node";

/* public static */ NabuTalk.Surround = function(text)
{
	return "<"+NabuTalk.surrounding+">"+text+"</"+NabuTalk.surrounding+">";
};

/* public static */ NabuTalk.UnSurround = (function()
{
	var regex = new RegExp("^.*<"+NabuTalk.surrounding+">(.*)</"+NabuTalk.surrounding+">$"); // Opera adds '<?xml ... ?>'

	return function(text)
	{
		return text.replace(regex, "$1");
	};
})();

/* ======= *
 * COOKIES *
 * ======= */

/* public static */ NabuTalk.ShowAllCookies = function()
{
	NabuTalk.Info("cookies: "+document.cookie);
};

/* public static */ NabuTalk.GetCookie = function(name)
{
	if ( typeof name != "string" ) name = name.source;
	var found = document.cookie.match(new RegExp("(^|; )"+name+"=([^;]*)")); // IE 5.x does not support "(?:...)".
	if ( found ) return unescape(found[2]);
	return "";
};

/* public static */ NabuTalk.SetCookie = function(name, value, days, path)
{
	var expires;
	if ( days ) {
		var date = new Date();
		date.setDate(date.getDate()+days);
		expires = "; expires="+date.toGMTString();
	} else {
		expires = "";
	}

	if ( !path ) path = "/";

	document.cookie = name+"="+escape(value)+expires+"; path="+path;
};

/* public static */ NabuTalk.ResetCookie = function(name, path)
{
	if ( typeof name != "string" ) {
		var found = document.cookie.match(new RegExp("(^|; )("+name.source+")=")); // IE 5.x does not support "(?:...)".
		if ( !found ) return false;
		name = found[2];
	}
	NabuTalk.SetCookie(name, "", -1, path);
	return true;
};

/* public static */ NabuTalk.AreCookiesEnabled = function()
{
	var cookie = "__nabutalk__";
	var value = NabuTalk.COMPANY+" "+Math.random();
	NabuTalk.SetCookie(cookie, value);
	if ( NabuTalk.GetCookie(cookie) == value ) {
		NabuTalk.ResetCookie(cookie);
		return true;
	} else {
		return false;
	}
};

/* ======================== *
 * BROWSER RELATED FEATURES *
 * ======================== */

/* public static */ NabuTalk.HTTPRequest = function()
{
	try {
		return new XMLHttpRequest();
	} catch ( e ) {
		try {
			return new ActiveXObject("Microsoft.XMLHTTP"); 
		} catch ( e ) {
			NabuTalk.NotSupported("NabuTalk.HTTPRequest()");
			return null;
		}
	}
};

/* public static */ NabuTalk.XMLParse = function(string, expectedRoot)
{
	var doc = null;
	try {
		doc = new DOMParser().parseFromString(string, "text/xml");
	} catch ( e ) {
		try {
			doc = new ActiveXObject("Microsoft.XMLDOM");
			doc.async = "false";
			doc.loadXML(string);
		} catch ( e ) {
			NabuTalk.NotSupported("NabuTalk.XMLParse(string, expectedRoot)");
		}
	}
	if ( doc ) {
		var root = doc.documentElement;
		if ( root && root.nodeName == expectedRoot && root.getElementsByTagName("parsererror").length == 0 ) return root;
	}
	return null;
};

/* public static */ NabuTalk.XMLSerialize = function(doc)
{
	try {
		return new XMLSerializer().serializeToString(doc);
	} catch ( e ) {
		try {
			return doc.xml;
		} catch ( e ) {
			NabuTalk.NotSupported("NabuTalk.XMLSerialize(document)");
			return null;
		}
	}
};

////////////////////////////////////////////////////////////////////////////////

/*****************
 * CLASS MANAGER *
 *****************/

/* class */ NabuTalk.Manager = function(url, cookie, refresh, callbacks) {

	if ( arguments.length != 4 ) {
		NabuTalk.Error("Syntax: NabuTalk.Manager(service-url, session-cookie, refresh-query, callbacks)");
		return;
	}

/* ====== *
 * FIELDS *
 * ====== */

/* private */ var that            = this;

/* private */ var _MODE_COOKIE    = "nabutalk-mode";

/* private */ var _callbacks      = {};
/* private */ var _push_enabled   = null;
/* private */ var _ready          = true;
/* private */ var _refresh_query  = refresh;
/* private */ var _session_cookie = cookie; // must be a string or a regexp, matching the server session cookie
/* private */ var _service_url    = url;
/* private */ var _timer          = null;

/* ========= *
 * CALLBACKS *
 * ========= */

/* private */ function _Initialize_Callbacks(callbacks)
{
	var list = [
		"cookies-disabled",
		"empty-utterance",
		"get-utterance",
		"instance-name",
		"no-answer",
		"not-ready",
		"preprocess-utterance",
		"process-answer",
		"process-url",
		"push-enabled",
		"push-page",
		"set-answer",
		"time-out",
		"wait-answer"
	];
	_callbacks = {
		"empty-utterance" : function()       { return true;   },
		"process-answer"  : function(answer) { return answer; },
		"process-url"     : function()       { return null;   },
		"push-enabled"    : function()       { return true;   },
		"wait-answer"     : function()       {                }
	};
	var n = list.length;
	for ( var i = 0; i < n; ++i ) {
		var name = list[i];
		if ( !_callbacks[name] ) {
			_callbacks[name] = _Undefined_Callback(name);
		}
	}
	that.SetCallbacks(callbacks);
};

/* private */ function _Undefined_Callback(name)
{
	return function() {
		var message = "Callback \""+name+"\" has not yet been defined!\n";
		var n = arguments.length;
		for ( var i = 0; i < n; ++i ) {
			message += "\nargument "+i+": "+arguments[i];
		}
		NabuTalk.Error(message);
		return null;
	};
};

/* public */ this.SetCallback = function(name, callback)
{
	if ( !_callbacks[name] ) {
		NabuTalk.Error("\""+name+"\" is not a valid callback name!");
	} else if ( typeof callback != "function" ) {
		NabuTalk.Error("The callback for \""+name+"\" must be a function!");
	} else {
		_callbacks[name] = callback;
	}
};

/* public */ this.SetCallbacks = function(callbacks)
{
	for ( var name in callbacks ) {
		this.SetCallback(name, callbacks[name]);
	}
};

/* private */ function _Callback(name)
{
	try {
		return _callbacks[name];
	} catch ( e ) {
		NabuTalk.Exception("_Callback(\""+name+"\")", e);
		return null;
	}
};

/* ===== *
 * QUERY *
 * ===== */

/* private */ function _Query(query)
{
	that.ClearTimer();
	_ready = false;
	_Callback("wait-answer")();
	query = _service_url+"?"+query+"&stamp="+new Date().getTime();
	// NabuTalk.Info("query: "+query);

	var request = NabuTalk.HTTPRequest();
	request.open("GET", query, true);
	request.onreadystatechange = _Handle_Response(request);
	request.send(null);
};

/* private */ function _Handle_Response(request)
{
	return function() {
		try {
			if ( request.readyState == 4 ) {
				_ready = true; // to do before push
				if ( request.status == 200 ) {
					var answer = _Callback("process-answer")(request.responseText);
					// NabuTalk.Info("answer: "+answer);
					if ( answer ) {
						var channels = _Process_Answer(answer);
						if ( channels ) {
							// NabuTalk.Info("answer: "+channels["balloon-text"]);
							if ( _push_enabled && channels["push"] ) {
								NabuTalk.SetCookie(_MODE_COOKIE, NabuTalk.Manager.MODE_PUSH);
								_Callback("push-page")(channels["push"], channels);
							} else {
								_Callback("set-answer")(channels["balloon-text"], channels, NabuTalk.GetCookie(_MODE_COOKIE));
							}
							return;
						}
					}
					// NabuTalk.Error("invalid answer: "+answer);
				}
				_Callback("no-answer")(answer);
			}
		} catch ( e ) {
			NabuTalk.Exception("_Handle_Response()", e);
		}
	};
};

/* private */ function _Really_Interpret(utterance, link)
{
	if ( utterance != "" || _Callback("empty-utterance")() ) {
		if ( navigator.cookieEnabled ) {
			if ( _ready ) {
				var query;
				try {
					query = encodeURIComponent(utterance); // IE 5.0 does not have encodeURIComponent()
				} catch ( e ) {
					var replace = [
						[/[\xC0-\xC5]/g, "A"], [/[\xE0-\xE5]/g, "a"], 
						[/[\xC8-\xCB]/g, "E"], [/[\xE8-\xEB]/g, "e"],
						[/[\xCC-\xCF]/g, "I"], [/[\xEC-\xEF]/g, "i"],
						[/[\xD2-\xD6]/g, "O"], [/[\xF2-\xF6]/g, "o"],
						[/[\xD9-\xDC]/g, "U"], [/[\xF9-\xFC]/g, "u"],
						[/\xC7/g, "C"], [/\xE7/g, "c"], [/\xD1/g, "N"], [/\xF1/g, "n"], 
						[/\xC6/g, "AE"], [/\xE6/g, "ae"], [/\u0152/g, "OE"], [/\u0153/g, "oe"], 
						[/\xB2/g, "2"], [/\u20AC/g, " euro "],
						[/[^\x20-\x7F]/g, " "] ];
					var n = replace.length;
					for ( var i = 0; i < n; ++i ) {
						var pair = replace[i];
						utterance = utterance.replace(pair[0], pair[1]);
					}
					query = escape(utterance); // escape() handles correctly only ASCII-7 characters
				}
				var query = "utterance="+query;
				var parameters = _Callback("preprocess-utterance")();
				if ( parameters ) query += "&"+parameters;
				if ( link ) query += "&nl-link=yes";
				_push_enabled = _Callback("push-enabled")();
				NabuTalk.SetCookie(_MODE_COOKIE, NabuTalk.Manager.MODE_UTTERANCE);
				_Query(query);
			} else {
				_Callback("not-ready")();
			}
		} else {
			_Callback("cookies-disabled")(true);
		}
	}
};

/* public */ this.Interpret = function()
{
	_Really_Interpret(_Callback("get-utterance")(), false);
};

/* public */ this.InterpretLink = function(utterance)
{
	_Really_Interpret(utterance, true);
};

/* public */ this.Event = function(event, parameters)
{
	NabuTalk.SetCookie(_MODE_COOKIE, NabuTalk.Manager.MODE_EVENT);
	var query = "event="+event;
	if ( parameters ) query += "&"+parameters;
	_Query(query);
}

/* public */ this.Refresh = function()
{
	if ( _refresh_query !== null ) {
		if ( NabuTalk.AreCookiesEnabled() ) {
			_push_enabled = false; // no push on welcome or when refreshing display
			var mode = NabuTalk.GetCookie(_MODE_COOKIE);
			if ( mode != NabuTalk.Manager.MODE_PUSH ) { // "MODE_PUSH" indicates we have not displayed the answer yet.
				NabuTalk.SetCookie(_MODE_COOKIE, mode ? NabuTalk.Manager.MODE_REFRESH : NabuTalk.Manager.MODE_FIRST);
			}
			_Query(_refresh_query);
		} else {
			_Callback("cookies-disabled")(false);
		}
	}
};

/* public */ this.NewDialogue = function()
{
	NabuTalk.ResetCookie(_session_cookie);
	NabuTalk.ResetCookie(_MODE_COOKIE);
	this.Refresh();
	// NabuTalk.Info("cookie: "+NabuTalk.GetCookie(_session_cookie));
};

/* ================= *
 * ANSWER PROCESSING *
 * ================= */

/* private */ function _Process_Answer(answer)
{
	var doc = NabuTalk.XMLParse(answer, "answer");
	if ( !doc ) return null;
	var result = {};
	// Get the properties
	result["properties"] = {};
	var properties = doc.firstChild;
	if ( properties && properties.nodeName == "nabutalk" ) {
		propertyElements = properties.childNodes;
		var n = propertyElements.length;
		for ( var i = 0; i < n; ++i ) {
			var property = propertyElements[i];
			result["properties"][property.nodeName] = property.firstChild.nodeValue;
		}
	}
	// Parse the channels
	var balloon;
	var channelElements = doc.getElementsByTagName("channel");
	var n = channelElements.length;
	for ( var i = 0; i < n; ++i ) {
		var channel = channelElements[i];
		var name = channel.getAttributeNode("name");
		if ( name ) {
			if ( name.value == "balloon" ) {
				if ( channel.childNodes.length != 1 ) return null;
				var data = channel.firstChild.data; // extract "<![CDATA[...]]>" content
				if ( !data ) return null;
				balloon = NabuTalk.XMLParse(NabuTalk.Surround(data), NabuTalk.surrounding);
				if ( !balloon ) { // 'data' is not valid XML
					data = NabuTalk.ReplaceXMLEntities(data);
					balloon = NabuTalk.XMLParse(NabuTalk.Surround(data), NabuTalk.surrounding);
					if ( !balloon ) return null;
				}
				// NabuTalk.Info("balloon: "+NabuTalk.XMLSerialize(balloon));
			} else { // "push", "avatar"...
				result[name.value] = channel.firstChild.nodeValue;
			}
		}
	}
	if ( !balloon ) return null;
	// Process the balloon content
	var instanceName;
	var aElements = balloon.getElementsByTagName("a");
	for ( var i = aElements.length; i-- > 0; ) { // start from end since this loop may remove elements
		var a = aElements[i];
		var href = a.getAttributeNode("href");
		if ( href ) {
			var clazz = a.getAttributeNode("class");
			if ( clazz && clazz.value == "nl-link" ) {
				if ( !instanceName ) instanceName = _Callback("instance-name")();
				a.setAttribute("onclick", instanceName+".InterpretLink('"+href.value.replace(/'/g, "\\'")+"'); return false;");
				a.setAttribute("href", ""); // after using "href.value"
			} else {
				var url = _Callback("process-url")(href.value, a);
				if ( url ) a.setAttribute("href", url);
			}
		}
		if ( !a.hasChildNodes() ) {
			a.parentNode.removeChild(a);
		}
	}
	result["balloon"] = balloon;
	result["balloon-text"] = NabuTalk.UnSurround(NabuTalk.XMLSerialize(balloon));
	return result;
}

/* ===== *
 * TIMER *
 * ===== */

/* private */ this.ClearTimer = function()
{
	if ( _timer ) {
		// NabuTalk.Debug("no timer | "+new Date().getTime());
		clearTimeout(_timer);
		_timer = null;
	}
}

/* public */ this.SetTimer = function(ms)
{
	this.ClearTimer();
	if ( ms > 0 ) {
		// NabuTalk.Debug(ms+" ms | "+new Date().getTime());
		_timer = setTimeout("("+_Callback("time-out").toString()+")()", ms);
	}
};

/* === *
 * LOG *
 * === */

/* public */ this.Log = function(value)
{
	var request = NabuTalk.HTTPRequest();
	request.open("GET", _service_url+"?log="+encodeURIComponent(value), true);
	request.send(null);
};

/* ============= *
 * INTIALIZATION *
 * ============= */

_Initialize_Callbacks(callbacks);

}; /* function NabuTalk.Manager */

/* ========= *
 * CONSTANTS *
 * ========= */

/* public static */ NabuTalk.Manager.MODE_FIRST     = 0;
/* public static */ NabuTalk.Manager.MODE_REFRESH   = 1;
/* public static */ NabuTalk.Manager.MODE_PUSH      = 2;
/* public static */ NabuTalk.Manager.MODE_UTTERANCE = 3;
/* public static */ NabuTalk.Manager.MODE_EVENT     = 4;

/* ================= *
 * INSTANCE CREATION *
 * ================= */

/* public static */ NabuTalk.Manager.CreateInstance = function(name, url, cookie, refresh, callbacks)
{
	var nabutalk = new NabuTalk.Manager(url, cookie, refresh, callbacks);
	nabutalk.SetCallback("instance-name", function () { return name; });
	window[name] = nabutalk;
};

////////////////////////////////////////////////////////////////////////////////

/****************
 * CLASS AVATAR *
 ****************/

/* class */ NabuTalk.Avatar = function(id, listener, pattern, idleAnimation)
{
	if ( arguments.length != 4 ) {
		NabuTalk.Error("Syntax: NabuTalk.Avatar(object-id, listener, url-pattern, idle-animation)");
		return;
	}

/* ====== *
 * FIELDS *
 * ====== */

/* private */ var that             = this;

/* private */ var _avatar          = null;
/* private */ var _idle_animation  = idleAnimation;
/* private */ var _last_position   = null;
/* private */ var _loaded_callback = null;
/* private */ var _parameters      = {};
/* private */ var _pattern         = pattern;
/* private */ var _repeat          = false;
/* private */ var _ticks           = 0;
/* private */ var _url             = null;

/* ========== *
 * PARAMETERS *
 * ========== */

/* private */ function _Compute_URL(parameters)
{
	var url = _pattern;
	for ( var name in parameters ) {
		url = url.replace(new RegExp("{"+name+"\}", "g"), parameters[name]); // regexp required to replace all occurrences
	}
	return url;
};

/* public */ this.Get = function(name /* optional */)
{
	return name ? _parameters[name] : _parameters;
};

/* public */ this.Set = function(parameters)
{
	// Set new parameters
	var change = true;
	for ( var name in parameters ) {
		var value = parameters[name];
		if ( value && _parameters[name] != value ) {
			_parameters[name] = value;
			change = true;
		}
	}
	if ( change ) {
		// Check whether the URL has changed and all of its parameters are instanciated
		var url = _Compute_URL(_parameters);
		if ( _url != url && url.indexOf("{") < 0 ) {
			_url = url;
			return true;
		}
	}
	return false;
};

/* public */ this.Update = function(parameters, repeat)
{
	_repeat = repeat;
	if ( this.Set(parameters) ) this.Start();
};

/* ==== *
 * PLAY *
 * ==== */

/* private */ function _Check()
{
	// Flash animations may be blocked by some browsers/plugins.
	return _url && _avatar;
};

/* public */ this.Start = function()
{
	if ( !_Check() ) return;
	// Change the video currently played
	_last_position = "start"; // to avoid overlap with "_Update_Listener"
	try {
		_avatar.SetVariable("method:stop", "");
		_avatar.SetVariable("method:setUrl", _url);
		_avatar.SetVariable("method:play", "");
	} catch ( e ) {
		_avatar = null; // "SetVariable()" does not work on IE 5.x
	}
};

/* public */ this.Repeat = function()
{
	if ( !_Check() ) return;
	// Continue to play the same video
	_avatar.SetVariable("method:setPosition", "0");
};

/* =============== *
 * UPDATE LISTENER *
 * =============== */

/* private */ function _Update_Listener()
{
	// "this" is the Flash object.
	// NabuTalk.Debug(this.isPlaying + " | " + this.url + " (" + this.bytesPercent + " %) | " + (_repeat ? "repeat" : "once") + " | " + this.position + " / " + this.duration + " (" + _last_position + ") | " + "#" + (_ticks++));
	if ( this.bytesPercent == "100" ) {
		if ( this.position != _last_position ) {
			_last_position = this.position;
			if ( _loaded_callback ) _loaded_callback();
		} else {
			_last_position = "end";
			if ( _repeat ) {
				that.Repeat();
			} else {
				that.Update({animation: _idle_animation}, true);
			}
		}
	} else if ( this.isPlaying == "false" ) {
		that.Start();
	}
}

/* ============= *
 * MISCELLANEOUS *
 * ============= */

/* public */ this.SetLoadedCallback = function(callback)
{
	_loaded_callback = callback;
};

/* public */ this.Cache = function(/* parameters... */)
{
	var n = arguments.length;
	for ( var i = 0; i < n; ++i ) {
		var request = NabuTalk.HTTPRequest();
		request.open("GET", _Compute_URL(arguments[i]), true);
		request.send(null);
	}
};

/* ============== *
 * INITIALISATION *
 * ============== */

_avatar = NabuTalk.$(id);
listener.onUpdate = _Update_Listener;

}; /* function NabuTalk.Avatar */

/* ============== *
 * EMPTY LISTENER *
 * ============== */

/* public static */ NabuTalk.Avatar.EmptyListener = function()
{
	return {
		onInit     : function() { },
		onFinished : function() { },
		onClick    : function() { },
		onKeyUp    : function() { },
		onUpdate   : function() { }
	};
};

