/**
 * This script is a simple message router that connects to a server, retrieves messages and then 
 * dispatches messages to listeners.
 */

function EventSource()
{
	var id;
	var listener;
	var currentResource;
	var originalResource;
}

function Message()
{
	this.content=new Object();
}

function ElementListener(element)
{
	this.element = element;
	this.onMessage = function(msg)
	{
		var code;
		code = this.element.getAttribute("onMessage");
		
		// note: This requires that onMessage= code snippets use a 'msg' parameter.
		// @todo: Determine a better dispatch mechanism, perhaps using closures.
		eval(code);
	}
}

function xr_doLoad()
{
	xml_hookMozXPath();

	var router = new ElementRouter(document);
	router.start();
}

/**
 * This object will find HTML elements, register them as listeners to remote messages 
 * and start a MessageConnection background process that retrieves and dispatches messages. 
 */
function ElementRouter(document)
{
	this.document = document;
	this.connection = new MessageConnection();

	// find all HTML element listeners and start message processing.
	this.start = function()
	{
		try
		{
			var src = new EventSource();
			
			// @todo: get all elements, not just one by id
			var element;
			element = document.getElementById("listener");
			if (element)
			{
				// @todo: should this remember any data at this point, or check every time
				src.originalResource = element.getAttribute("event-src");
				src.currentResource = src.originalResource; 
			}
			src.listener = new ElementListener(element);
			this.connection.addEventSource(src);
		}
		catch(e)
		{
			console_out("ERROR: start() : "+e.message);
		}
		this.connection.start();
	}
}

/**
 * This object connects to a remote server, retrieves messages 
 * and dispatches to registered listeners.
 * 
 */
function MessageConnection()
{
	
	var location;
	var start;
	var eventSources;
	var eventSourceLoopId;
	var handleEvents;

	// need to fixup the XPath capabilities.
	xml_hookMozXPath();

	this.eventSources=new Array();

	this.addEventSource = function(source)
	{
		source.id = this.eventSources.length;
		this.eventSources[this.eventSources.length] = source;		
	}
	
	/**
	 * Begin a background process that retrieves and dispatches messages.
	 */
	this.start = function() {
		// start a background loop to process each event source
		var self=this;
		setTimeout(function() {self.startEventLoop() },100);		
	}

	/**
	 * For each event-source, read the next block of messages.
	 */
	this.startEventLoop = function() 
	{
		try
		{
			for (i=0; i < this.eventSources.length; i++)
			{
				this.processEventSource(this.eventSources[i]);
			}
		}
		catch(e)
		{
			console_out("ERROR: startEventLoop() : "+e.message);
		}
	}
	
	this.processEventSourceById = function(source_id) {
		this.processEventSource(this.eventSources[source_id]);
	}

	/**
	* return XMLHttpRequest independent of browser
	*/
	this.getHttpClient = function()
	{
		if (window.ActiveXObject) 
			return new ActiveXObject("Microsoft.XMLHTTP");
		return new XMLHttpRequest();
	}

	/**
	 * Retrieve the next set of messages for an event source. This is a non-blocking call.
	 */
	this.processEventSource = function(source)
	{
		var code;
		var xhr;
		var self;
		
		try
		{
			
			{
				// fetch the set of messages to process			
				try
				{	
					xhr = this.getHttpClient();

					// set up callback that is invoked when messages arrive
					self = this;
					xhr.onreadystatechange=function() {
						if (xhr.readyState == 4)
						{
							// don't use "this.handleEvents", our context is blown in this closure
							self.onMessages(source,xhr);
						}
					}
					// send the request for more messages
					console_out("retrieving "+source.currentResource);
					xhr.open("GET",source.currentResource,true);
					xhr.send(null);
				}
				catch(e)
				{
					console_out("ERROR: processEventSource() : "+e.message);
				}
			}
		}
		catch(e)
		{
			console_out("ERROR: processEventSource() : "+e.message);
		}
	}

	/**
	 * This method is called when incoming messages have arrived.
	 */
	this.onMessages = function(source,xhr)
	{
		var msg;
		var xml;

		console_out("dispatching "+source.currentResource);

		// dispatch each message
		// @todo: create content-type independent processing
		try
		{
			var events;
			
			events = xhr.responseXML.documentElement.selectNodes("message");
			for (i=0; i < events.length; i++)
			{
				xml = events[i];
				msg = new Message();
				
				msg.uri = xml.getAttribute("resource");
				msg.method = new String(xml.getAttribute("method")).toUpperCase();
				msg.content.type = new String(xml.getAttribute("content-type"));
	
				// @todo: turn the Element node back to text?
				if ((msg.content.type == "application/xml") || (msg.content.type == "text/xml"))
				{
					msg.content.body = xml;
				}
				else
				{
					if (xml.firstChild)
					{
						msg.content.body = xml.firstChild.data;
						msg.content.length = msg.content.body.length;
					}
				}
				
				
				if (source.listener)
				{
					try
					{
						source.listener.onMessage(msg);
					}
					catch(e)
					{
						console_out("ERROR: listener.onMessage() "+e.message);
					}
				}
				/*
				if (msg.uri)
				{
					if (document.getElementById(msg.uri))
						target = document.getElementById(msg.uri);
				}

				if (target)
				{				
					code = target.getAttribute("onMessage");
	
					// note: This requires that onMessage= code snippets use a 'msg' parameter.
					// @todo: Determine a better dispatch mechanism, perhaps using closures.
					eval(code);
				}
				*/
			}
		}
		catch(e)
		{
			console_out("ERROR: onMessages() : "+e.message);
		}

		try
		{
			// use the 'next' pointer if it exists, otherwise the current resource will be re-fetched
			var next = xhr.responseXML.documentElement.selectSingleNode("@next");
			if (next)
			{
				var uri;
				
				uri = xr_resolveURI(source.currentResource,next.value);
				source.currentResource = uri;
				// source.element.setAttribute("event-src",uri);
			}

			// set a timeout function to retrieve the next block of messages
			// the closure function needs a better context than just 'this'.... 
			var self=this;
			setTimeout(function() {self.processEventSourceById(source.id);},200);
		}
		catch(e)
		{
			console_out("ERROR: onMessages() : "+e.message);
		}
		

	}
}

/**
 * Do some URL text manipulation.
 */
function xr_resolveURI(base,relative)
{
	var path;
	var query;
	var uri;

	try
	{
		var parts;
		parts = base.split("?");
		base = parts[0];
		query = parts[1];
		
		parts = relative.split("?");
		if (parts[0])
			uri = parts[0];
		else
			uri = base;
			
		if (parts[1])
			uri += "?"+parts[1];
		/*
		if (query)
			uri += "&"+query;
		*/
	}
	catch(e)
	{
		console_out("xr_resolveURI() : "+e.message);	
	}
	return uri;
}


