/*
//---------------------------------------------------------------------------------------------- 
// Jda Emulator. (c) Peter Svensson psvensson@gmail.com 2007.Licensed under LGPL 
// This is a simple emulator/stub for the Javascript Dataflow Architecture, since it hasn't been released yet. :-P

http://www.gnu.org/copyleft/lesser.html
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//---------------------------------------------------------------------------------------------- 
*/

	//--------------------------------------------------- Support functions ---------------------------------------------------------

	/*
    		Copyright Robert Nyman, http://www.robertnyman.com
    		Free to use if this text is included
	*/
	getElementsByAttribute = function(oElm, strTagName, strAttributeName, strAttributeValue)
	{
	    	var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
    		var arrReturnElements = new Array();
    		var oAttributeValue = (typeof strAttributeValue != "undefined")? new RegExp("(^|\\s)" + strAttributeValue + "(\\s|$)") : null;
    		var oCurrent;
    		var oAttribute;
    		for(var i=0; i<arrElements.length; i++)
		{
        		oCurrent = arrElements[i];
        		oAttribute = oCurrent.getAttribute && oCurrent.getAttribute(strAttributeName);
        		if(typeof oAttribute == "string" && oAttribute.length > 0)
			{
            		if(typeof strAttributeValue == "undefined" || (oAttributeValue && oAttributeValue.test(oAttribute)))
				{
                			arrReturnElements.push(oCurrent);
            		}
        		}
    		}
    		return arrReturnElements;
	}

	dynamicLoadScript = function(uuid, url) 
	{
   		var head = document.getElementsByTagName("head")[0];
   		script = document.createElement('script');
   		script.id = 'uploadScript_'+uuid;
   		script.type = 'text/javascript';
   		script.src = url;
   		head.appendChild(script)
 	}

	// Stolen from prototype;
	
	var $A = Array.from = function(iterable) 
	{
  		if (!iterable) return [];
  		if (iterable.toArray) {
	    	return iterable.toArray();
		} else {
    		var results = [];
    		for (var i = 0, length = iterable.length; i < length; i++)
      		results.push(iterable[i]);
    		return results;
  		}
	}

	
	Function.prototype.bind = function() {
	  var __method = this, args = $A(arguments), object = args.shift();
	  return function() {
	    return __method.apply(object, args.concat($A(arguments)));
	  }
	}
	
	
	
function Terminal(name, func, limit) 
{
	this.name = name;
	this.func = func;
	this.queue = new Array(limit);
}


function Infotron(id, name, node, uuid) 
{
		this.id = id; // The id member variable is the string identifier specified using the id attribute on the div element used to declare the infotron.
		this.name = name; // The name member variable is the string identifier speficified using the name attribute on the div element used to declare the infotron.
		this.dom_node = node; // Once an infotron is constructed, it is free to manipulate the div DOM node used for its declaration. The reference to this DOM node is found in a member variable named dom_node.
		this.bp_uuid = uuid; // The bp_uuid member variable is a string UUID of the blueprint which the infotron is an instance of.
		this.is_not_ready_for_message_pumping = true; // Sensible default, no doubt.
		this.in_terminals = new Array(); // one terminal for array element, indexed by name; in_terminals['termname'] = newterminalobject;
		this.out_terminals = new Array(); // No array, just string names, like out_terminals[0] = 'bar';
		this.virgin = true; // So tghat we know to call_onStartUp just before first message
		this.queue_lock = false; // Set by the first thread to call queueMessage on us, so all others will just push messages on the queues
   	
}
	// terminals_arr is just a string array of terminal names (in or out)
Infotron.prototype.blockAllTerminalsExcept =function(terminals_arr)
{

}

Infotron.prototype.unlockAllTerminals = function()
{

}

	// defines or redefines which function should be called when a message appears in the argument input terminal 
Infotron.prototype.onMsg = function(terminal_in, func)
{
	__star.debug("onMsg called to tie input terminal "+terminal_in+" to handler function "+func);
	var term = in_terminals[terminal_in];
	__star.debug("term == "+term);
	term.func = func;
}

	// This is used by the internal infotron logic to leave a message on the specific output terminal which, if connected to another
	// infotrons input terminal, will deliver the message there.
Infotron.prototype.postMessage = function(terminal_out, msg)
{
	// Get the array pointing out the target infotron and its input terminal
	var tarr = this.out_terminals[terminal_out];
	if (tarr == "not_connected")
	{
		__star.debug("Out terminal not connected. Not posting anything...");
		return;
	}
//TODO: Make this an array, so that it's possible to tie more than one target input infotron terminal to an output terminal
	var tinf = tarr[0];
	var tterm = tarr[1];
	__star.debug(" --------------> postMessage for infotron "+this.id+" to "+tinf);
	__star.queueMessage(tinf, tterm, msg);
	__star.debug(" <-------------- postMessage done for infotron "+this.id+" to "+tinf);
}

	// Pops the uppermost (earliest) message in the queue of the named input terminal. Used where? Input terminals are reactive aren't they?
Infotron.prototype.popMessage = function(terminal_in)
{
		
}

	// Gets called right before the first message appears.
Infotron.prototype._onStartUp = function()
{

}

	// If this is overridden, the original terminal handler will not be called for the named input terminal.
Infotron.prototype._onMsgDelivery = function(terminal_in, msg)
{
	__star.debug("*********** _onMsgDelivery for "+this.name+" called for delivering message "+msg+" to input terminal "+terminal_in);
	var term = this.in_terminals[terminal_in];
	__star.debug("term == "+term);
	var funcname = term.func;
	var func = this.prototype[funcname];
	func.call(this.prototype, msg); // Call the defined function
	__star.debug("*********** _onMsgDelivery finished.");
}

	// Gets called by the __star.shutDown() method for any additional cleanup for this infotron.
Infotron.prototype._onDestruct = function()
{

}

	/*
	The _onConstructInternal method is called by Star on a
	Domain Proxy infotron that receives a message before it has
	finished constructing its domain. This is the method overridden to
	lazily construct a domain inside an iframe or a window. The
	method does not take any arguments.
	*/
Infotron.prototype._onConstructInternal = function()
{

}

	/*
	The _onChildConstruct method is called by Star on a
	Domain Proxy infotron when its domain finishes constructing
	and calls the allowMessageToChild method. The first
	argument is the id of the element (i.e., iframe/window) that wraps
	the domain. The second and third arguments are input and output
	terminals that belong to the constructed domain.
	*/
Infotron.prototype._onChildConstruct = function(element_id, terminals_in, terminals_out)
{

}

	//--------------------------------------------------- Debug functions -----------------------------------------------------------
Infotron.prototype.error = function(txt)
{

}

Infotron.prototype.debug = function(txt)
{
		dojo.debug(txt);
}

Infotron.prototype.caution = function(txt)
{

}

Infotron.prototype.warning = function(txt)
{

}

Infotron.prototype.info = function(txt)
{

}




function Blueprint(uuid, t_in, t_out, impl, name) 
{
	this.uuid = uuid; 
	this.name = name; 
	this.terminals_in = t_in;
	this.terminals_out = t_out;
	this.implementation = impl; // The function to be called whenever an infotron with this blueprint gets created (by addInfotron()).
}



	/*

	How to call this function;


	BLUEPRINT(~6B5EAB0E6AD4483e84D3D5EF14C8AEE7,
	[
		[
			[trigger_in, onTrigger, 10]
		],
		[
			greeting_out
		],
		function(Class)
		{
			Class.prototype._onInit = function(props)
			{
				this.greeting = props[greeting];
			};
			Class.prototype.onTrigger = function(msg)
			{
				this.dom_node.innerHTML = this.greeting;
				this.postMessage(greeting_out, this.greeting);
			}
			return Class;
	}, Greeter Infotron);



	What happens when a new infotron is generated from the blueprint is that a new 'class' function is generated with an automatic name.
	This function is then passed to the blueprint implementation function, which extends it as it sees fit.	
	*/




var __w = ""; // The (current) window or frame object which the infotron resides in
var __d = ""; // Ditto for the document object


function __starimpl()
{
	// star events: bpmiss, bpload, afterbpload, boot, afterboot and shutdown.
	
	/*
	A bpmiss event is triggered when JS-Star tries to deliver a
	message to an infotron that is yet to be constructed because its
	blueprint was not found in memory. The context member is an
	array containing the id of the infotron that triggered the event and
	the UUID of the missing blueprint as the first and second items
	respectively.

	A bpload event is triggered when a new blueprint is loaded into
	memory via the function BLUEPRINT. The context member is
	an object literal with the members name, iterms, oterms
	and uuid containing the name of the blueprint, array of input
	terminal names, an array of output terminal names, and the UUID
	of the blueprint respectively.

	A afterbpload event is triggered after a new blueprint is loaded
	into memory via the function BLUEPRINT and all infotrons that
	were delayed construction have been constructed. The context
	member is an object literal with the members name, iterms,
	oterms and uuid containing the name of the blueprint, array
	of input terminal names, an array of output terminal names, and
	the UUID of the blueprint respectively.

	A boot event is triggered right before the infotrons start to get
	constructed as part of the __star.boot method call. The
	context member is the reference to the frame/window object in
	which JS-Star resides.

	An afterboot event is triggered right after all the infotrons are
	constructed as part of the __star.boot method call. The
	context member is set to null.

	A shutdown event is triggered after all the infotrons have been
	destructed as part of the __star.shutDown method call. The
	context member is set to null.

	*/

	//--------------------------------------------------- Debug functions -----------------------------------------------------------
	__starimpl.prototype.error = function(txt)
	{

	}

	__starimpl.prototype.debug = function(txt)
	{
		dojo.debug(txt);
	}

	__starimpl.prototype.caution = function(txt)
	{

	}

	__starimpl.prototype.warning = function(txt)
	{

	}

	__starimpl.prototype.info = function(txt)
	{

	}



	this.debug("** Initializing LGPL JDA Emulator 0.1 **");

	this.events = new Array();
	this.events['bpmiss'] = new Array();
	this.events['bpload'] = new Array();
	this.events['afterbpload'] = new Array();
	this.events['boot'] = new Array();
	this.events['afterboot'] = new Array();
	this.events['shutdown'] = new Array();

		
	this.__infotrons = new Array();
	this.__blueprints = new Array();
	this.has_loaded = false;

	//--------------------------------------------- Domain stuff
	this.domain_uuid = "";
	this.domain_iterms = "";
	this.domain_oterms = "";
	this.domain_connections = "";
	//--------------------------------------------- 'Static' variables
	this.DEFAULT_QUEUE_SIZE = 50;
   
	//Calling the __star.boot method starts JS-Stars bootstrapping process. This method should be called below as soon as the DOM is ready. The method does not take any arguments.	
	this.boot = function()
	{
		this.debug("boot called");
		// Get any domain definition for the current page
		metatags = document.getElementsByTagName("meta");		
		for (cnt = 0; cnt < metatags.length; cnt++)
		{
		   	var name = metatags[cnt].getAttribute("name");
		   	var content = metatags[cnt].getAttribute("content");
		   	if (name == "uuid")
			{
				// We have a domain definition. Start to fill in the details
				this.domain_uuid = content;
			}	
			if (name == "iterms")
			{				
				this.domain_iterms = content;
			}
			if (name == "oterms")
			{				
				this.domain_oterms = content;
			}		
			if (name == "connections")
			{				
				this.domain_connections = content;
			}	
		}		
		var scarr = getElementsByAttribute(document.body, "*", "script");
		this.blueprint_count = scarr.length;
		for(var s in scarr)
		{
			this.debug("Skunk-checking element "+scarr[s].id+" for impl attribute..");
			var impl = scarr[s].attributes['impl'];
			this.debug("impl == "+impl);
			if (impl)
			{
				var script = scarr[s].attributes['script'].textContent;
				this.debug("Found script reference "+script+" for UUID "+impl.textContent);	
				dynamicLoadScript(impl.textContent, script);
			}
		}		
		
	}
	
	// Calling the __star.shutdown method removes all infotrons and cleans them up by calling their _onDestruct methods. At the end of this process the shutdown event for JS-Star is triggered. The method does not take any arguments.
	this.shutdown = function()
	{
		this.debug("shutdown called");
		// Spin over all registered infotrons		
		for(var it in this.infotrons)
		{			
			// Send each a shutdown message
			it._onDestruct();
			// And remove the reference to it
		}
	}

	this.addInfotron = function(domid, uuid, params, connsparam)
	{
		this.debug("addInfotron called for "+domid+", domid="+domid+", uuid="+uuid+", params="+params+", conns="+connsparam);
		// Get blueprint
		var bp = this.__blueprints[uuid];
		var node = document.getElementById(domid);
		//-------------------------- Here be dragons ----------------------------------
		var newinfotron = new Infotron(domid, name, node, uuid); 
		newinfotron.prototype = new Object;
		for(var p in newinfotron)
		{
			newinfotron.prototype[p] = newinfotron[p]; // Copy all variables
		}
		// Start the infotron (call the implementation)
		var newiclass = bp.implementation(newinfotron);
		//-----------------------------------------------------------------------------
		this.debug("Created new prototype infotron "+newinfotron);
		// Make a queue for each in terminal (The queues of the out terminals are the in_terminals they're connected to)		
		for(var terminal in bp.terminals_in)
		{
			var arr =  bp.terminals_in[terminal];
			var t_name = arr[0];
			var t_func = arr[1];
			var t_size = arr[2];	
			if (t_name)
			{ 
				this.debug("creating new input terminal name="+t_name+", func="+t_func+", queue size="+t_size);
				var term = new Terminal(t_name, t_func, t_size);
				newinfotron.in_terminals[t_name] = term;
			}
			else
			{
				this.debug("Skipping null input terminals ..");
			}
		}
		for(var terminal in bp.terminals_out)
		{
			var t_name = bp.terminals_out[terminal];
			this.debug("creating new output terminal name="+t_name);	
//TODO: Make this an array, so that it's possible to tie more than one target output infotron terminal to an input terminal		
			newinfotron.out_terminals[t_name] = "not_connected"; // This string will be replaced by a connection when added
		}	// A connection is an array with the first element the name/domid of the target input infotron, and the second element the input terminal.
		
		// Store the infotron
		this.__infotrons[domid] = newiclass;		
		this.debug("New infotron "+domid+" with uuid "+uuid+" created");	
		// Add any defined connections from this infotron to other named infotrons
		if (connsparam != 'undefined')
		{
			this.debug("Wiring this infotron with defined connections "+connsparam);
			eval("var conns = {"+connsparam+"}");
			this.debug("conns = "+conns);
			for (var input in conns)
			{
				var allc = conns[input];
				this.debug("Handling connection array "+allc+" under key "+input);
				var clength = allc.length;
				for (var j = 0; j < clength; j++)
				{
					var c = allc[j];
					this.debug("Storing specific connection "+j+"; "+c);
					var out = input;
					var inarr = c;
					var it_in = inarr[0];
					var term_in = inarr[1];
					if (term_in)
					{
						this.debug("Connecting the out terminal "+out+" for "+node+" with input terminal "+term_in+" at infotron "+it_in);
						var outarr = new Array(domid, out);
						this.connectTerminals(outarr, inarr);
					}
					else
					{
						this.debug("No connections defined for node "+node);
					}
				}
			}
		}	
		var par = "";
		if (params && params != 'undefined')
		{
			eval("par={"+params+"}"); 
		}
		newiclass.prototype._onInit(par);
		this.debug("*************************************************************************");
		return [newinfotron.in_terminals, newinfotron.out_terminals];
	}

	this.removeInfotron = function(domid)
	{
		this.debug("removeInfotron called (unimplemented)");
		// Get all output terminals

		// Get all connections

		// If the target infotron exists, remove reference to it

		// remove infotron
	}

	this.addIterm = function(domid, iterm_name)
	{
		this.debug("addIterm called for "+domid+" term name == "+iterm_name);
		
		var term = new Terminal(iterm_name, "function_not_defined", this.DEFAULT_QUEUE_SIZE); // It _might_ be a good idea to be able to add the func as well.
		var it = this.__infotrons[domid];
		it.in_terminals[iterm_name] = term;
	}

	this.addOterm = function(domid, oterm_name)
	{
		this.debug("addOterm called for "+domid+" term name == "+oterm_name);
		var it = this.__infotrons[domid];
//TODO: Make this an array, so that it's possible to tie more than one target output infotron terminal to an input terminal		
		it.out_terminals[oterm_name] = "not_connected";
	}

	// Each array has two elements; 0 - domid / infotron name, 1 - terminal name
	this.connectTerminals = function(outarr, inarr)
	{
		var outid = outarr[0];
		var outterm = outarr[1];
		var inid = inarr[0];
		var interm = inarr[1];
		this.debug("connectTerminals called inid = "+inid+", interm = "+interm+", outid = "+outid+", outterm = "+outterm);
		// Build and add the target input array to the output terminal
		var it = this.__infotrons[outid];
		it.out_terminals[outterm] = inarr;
	}

	// Same same, but different
	this.disconnectTerminals = function(outarr, inarr)
	{
		this.debug("disconnectTerminals called (unimplemented)");

	}

	this.loadInfotrons = function()
	{
		if (this.has_loaded == true)
		{
			return;
		}
		this.has_loaded = true;
		this.debug("----------- Loading infotrons -------------");
		var elmarr = getElementsByAttribute(document.body, "*", "impl");
		for(var em in elmarr)
		{
			var impl = elmarr[em].attributes['impl']; // uuid of he blueprint which implements the functionaliy. D0h!
			var props = elmarr[em].attributes['properties'];
			var script = elmarr[em].attributes['script']; 
			var proxy = elmarr[em].attributes['is_proxy']; //TODO: Handle this too.
			var conns = elmarr[em].attributes['connections'];
			this.debug("Attempting to add infotron "+elmarr[em].id+" with blueprint "+impl.textContent);
			// See if we alrady have that blueprint
			var bp = this.__blueprints[impl.textContent]
			//debugger;
			if (!bp)
			{
				// Nope. Get the script and execute it to add the blueprint
				this.debug("blueprint "+impl.textContent+" not found. Attempt to load from provided script reference "+script.textContent);
				dynamicLoadScript(impl.textContent, script.textContent);
				// Now it's there (or else!! :)
				bp = this.__blueprints[impl.textContent];
				if (!bp)
				{
					// Act all irritated for a while
					this.debug("Blueprint "+impl.textContent+" still missing even after forced loading from infotron "+elmarr[em].id+" script file "+script.textContent);
					this.dispatchEvent({type: "bpmiss"});					
				}
				else
				{
					this.debug("blueprint "+impl.textContent+" loaded correctly");
					this.dispatchEvent({type: "bpload"});
				}
			}
			// TODO: Handle is_proxy
		
			// Add infotron to collection
			var name = (elmarr[em].id) ? elmarr[em].id : 'undefined';
			var uuid = impl ? impl.textContent : 'undefined';
			var properties = props ? props.textContent : 'undefined';
			var connections = conns ? conns.textContent : 'undefined';
			this.addInfotron(name, uuid, properties, connections);			
		}
	}

	// This function is considered 'private' and tries to find the infotron, and lazily creates it if it doesn't
	this.findInfotron = function(domid)
	{
		var it = this.__infotrons[domid];
		if (!it)
		{
			// Get all elements which define infotrons
			this.loadInfotrons();
			 it = this.__infotrons[domid]; // Retry resulotion
		}
		return it;
	}
	
	//----------------------------------------------------------
	// This is the main function used to send messages to infotrons. 
	//----------------------------------------------------------
	this.queueMessage = function(domid_in, terminal_in, msg)
	{
		this.debug("queueMessage called for "+domid_in+" terminal "+terminal_in);
		var it = this.findInfotron(domid_in);
		this.debug("infotron resolved to "+it.id);
		var term = it.in_terminals[terminal_in];
		this.debug("input terminal resolved to "+term.name);
		term.queue.push(msg);
		this.debug("message delivered on queue of input terminal");
		if (it.queue_lock == true)
		{
			// Another thread has locked the handling of messages. We'll just push our message on the queue.
			this.debug("Another thread is handling the input queues of all terminals for infotron "+domid_in+". Relaxing and taking a drink");
		}
		else
		{
			it.queue_lock = true; // We're the thread that handles all messages on all input terminal queues until exhausted
			this.debug("We are the first thread sending messages to infotron "+domid_in+" Locking quueues and rolling up sleeves.");
			var msgs = 0;
			this.debug("Checking queue sizes");
			for(var t in it.in_terminals)
			{
				var term = it.in_terminals[t];
				for(var m in term.queue)
				{
					var msg = term.queue[m];
					this.debug("delivering message "+msg+" to handler function for input terminal "+term.name);
					//term.func(msg); No, that was way too easy, wasn't it. 
					if (it.virgin == true)
					{
						it.virgin = false;
						it._onStartUp();
					}
					it._onMsgDelivery(term.name, msg); // The _onMsgDelivery will have too look up the terminal for itself . So there
				}
			}
			this.debug("All queues empty. Relasing lock on infotron "+domid_in);
			it.queue_lock = false; // Release lock on infotron
		}
	}

	//----------------------------------------------------------
	// Domain functions
	//----------------------------------------------------------

	// Probably delivers msg from a 'normal' infotron to the (current?) domains specific in terminal, for further routing
	this.receiveMessage = function(domain_in_terminal, msg)
	{
		this.debug("receiveMessage called (unimplemented)");

	}


	this.deliverMessage = function(window_reference, terminal_out, msg)
	{
		this.debug("deliverMessage called (unimplemented)");

	}

	// Tells the parent of the child domain, that in is ready top play ball, with in and out terminal names in repective arrays.
	this.allowMessagingToChild = function(child_window_ref, inp_arr, out_arr)
	{
		this.debug("allowMessagingToChild called for ");

	}

	//----------------------------------------------------------
	// Event functions
	//----------------------------------------------------------

	this.addEventListener = function(event_name, func)
	{
		this.debug("addEventListener called for event "+event_name);
		var evarr = events[event_name];
		evarr.push(func);
	}

	// event_obj; {type: event_name}
	this.dispatchEvent = function(event_obj)
	{
		this.debug("dispatchEvent called (unimplemented)");

	}

}

var __star = new __starimpl();


	function BLUEPRINT(uuid, in_terminals, out_terminals, impl, bpname)
	{
		//dojo.debug("Entering blueprint...");
		// uuid is... the UUID, in_ and out_terminals are arrays looking like; [name, handler_function_name, queue_size]
		// impl is a function defining the implementation, and bpname is an optional name for the blueprint
		var newbp = new Blueprint(uuid, in_terminals, out_terminals, impl, bpname);
		//debugger;
		__star.__blueprints[uuid] = newbp;
		dojo.debug("BLUEPRINT called. New blueprint created with uuid "+uuid+", name "+bpname);
		__star.blueprint_count -= 1;
		if (__star.blueprint_count < 1)
		{ 
			__star.loadInfotrons();
		}
	}

