// -- stand-alone xf_data variant for Medialab Solutions BV for use in ABL
// -- (c) 2004-5 xfinitegames by Arthur Langereis

/* ================ Util == */

var _XFCache = {};

function IsString(x) { return typeof(x)=="string"; }
function IsNumber(x) { return typeof(x)=="number"; }
function IsBool(x)   { return typeof(x)=="boolean"; }
function IsArray(x)  { return (null != x) && (x.constructor == Array); }
function IsObject(x) { return (typeof(x)=="object") && !IsArray(x); }

function AsArray(x)  { if(null == x) return []; if(!IsArray(x)) return [x]; else return x; }

function CreateXMLHttpRequest() {
	var xhr = null;
	if(window.XMLHttpRequest) {
		try { xhr = new XMLHttpRequest(); } catch(e) { }
	} else if(window.ActiveXObject) {
		try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e){ }
	}

	return xhr;
}

function CanUseXMLHttpRequests() {
	var xhr = CreateXMLHttpRequest();
	return xhr != null;
}

function FetchContentAndReplaceElement(url,elemOrID,dataToSend,callback) {
	setTimeout(function() {
		_FetchContentAndReplaceElement(url,elemOrID,dataToSend,callback);
	}, 50);
}

function FetchContentAndReplaceElementInFrame(url,elemOrID,doc,dataToSend) {
	setTimeout(function() {
		_FetchContentAndReplaceElementInFrame(url,elemOrID,doc,dataToSend);
	}, 50);
}

function FetchContentAndReplaceElementInFrameCached(url,elemOrID,doc,dataToSend) {
	setTimeout(function() {
		_FetchContentAndReplaceElementInFrameCached(url,elemOrID,doc,dataToSend)
	}, 50);
}

function _FetchContentAndReplaceElement(url,elemOrID,dataToSend,callback)
{
		if (UA_SAFARI) {
			var resp = DataDocument.LoadFromServer(url, "text", null, dataToSend);
			if (resp.request.readyState == 4) {
				var e = document.getElementById(elemOrID);
				if (!e) return;
				e.innerHTML = resp.request.responseText; 
				if (callback) {
					setTimeout(callback, 50);
				}
			}		
			else {
				e.innerHTML = "";
			}
		}
		else { // async
	 		DataDocument.LoadFromServer(url, "text", function() {
				if (this.request.readyState == 4) {
				   if(typeof(elemOrID) == "string") {
					   elemOrID = elemOrID.trim();
					   var e = document.getElementById(elemOrID);
					   if(!e) { 
					     alert("Element \"" + elemOrID + "\" does not exist.");
				        return;
				      }
				      elemOrId=e;
						elemOrId.innerHTML = this.request.responseText;
						if (callback) {
							setTimeout(callback, 50);
						}
				   }
				}
			}, dataToSend);
		}

}

function _FetchContentAndReplaceElementInFrame(url,elemOrID,doc,dataToSend)
{
 		var resp = DataDocument.LoadFromServer(url, "text", function() {
			if (this.request.readyState == 4) {
			   if(typeof(elemOrID) == "string") {
				   elemOrID = elemOrID.trim();
				   var e = doc.getElementById(elemOrID);
				   if(!e) { 
				     alert("Element \"" + elemOrID + "\" does not exist.");
			        return;
			      }
			      elemOrId=e;
					elemOrId.innerHTML = this.request.responseText;
			   }
			}
		}, dataToSend);
		
}

function _FetchContentAndReplaceElementInFrameCached(url,elemOrID,doc,dataToSend)
{
	   elemOrID = elemOrID.trim();

 	   if (_XFCache[url]) {
		   var e = doc.getElementById(elemOrID);
			e.innerHTML = _XFCache[url];
 	   }
 	   else {
 		   var resp = DataDocument.LoadFromServer(url, "text", function() {
			   if (this.request.readyState == 4) {
			      if(typeof(elemOrID) == "string") {
				      var e = doc.getElementById(elemOrID);
				      if(!e) { 
				        alert("Element \"" + elemOrID + "\" does not exist.");
			           return;
			         }
			         elemOrId=e;
					   elemOrId.innerHTML = this.request.responseText;
					   _XFCache[url] = this.request.responseText;
			      }
			   }
		   }, dataToSend);
 	   }
		
}


/* ================ Constants == */
// -- DataNode Flag Bitvalues
var DN_ISATTR	= 1;
var DN_ISTEXT	= 2;


// -- DataDocument load management
var DD_MAX_CONCURRENT_REQUESTS = (UA_MSIE ? 1 : 4); // IE _will_ block other stuff from loading while more than 1 request is open


// -- Common MIME types for easy access
var MIMETYPE_WWW_FORM = "application/x-www-form-urlencoded";


/* ================ DataDocument == */

DataDocument = {};

DataDocument.State = {
	Items: [],		// array of actual composite request records
	Pending: [],	// _indices_ into Items of pending async requests
	Active: [],		// _indices_ into Items of currently executing async requests
	_NextIndex: 0,	// next item index
	GetNextIndex: function() { return ++this._NextIndex; }
}


DataDocument.DocumentLoaded = function(index) {
	var reqobj = this.State.Items[index];
	if(!reqobj) { /*alert("DataDocument.DocumentLoaded was called for invalid request-index " + index  + ".");*/ return; }
	this.State.Items[index] = null;

	// -- remove index from Active list
	var ai = -1;
	for(var ae=0; ae<this.State.Active.length; ae++)
		if(this.State.Active[ae] == index) ai = ae;
	if(ai>-1) this.State.Active.splice(ai, 1);

	var root = null;
	if(null == reqobj.request.status) {	// Safari 1.3.0 and 2.0.0 bug...
		reqobj.request.status = 200;
		reqobj.request.statusText = "OK";
	}
	
	var ok = (reqobj.request.status == 200);
	if(ok) {
		if(reqobj.dataType == "xml")
			root = this.LoadFromXMLDocument(reqobj.request.responseXML);
		if(reqobj.dataType == "text")
			root = reqobj.request.responseText;
	}

	if(reqobj.func)
		reqobj.func(reqobj.request, root, ok);

	this.TryNextDocument();

	return { request: reqobj.request, data: root, ref: -1, OK: ok };
}


DataDocument.OnReadyStateChange = function(index) {
	var reqobj = this.State.Items[index];
	if(!reqobj) { /*alert("DataDocument.OnReadyStateChange was called for invalid request-index " + index  + ".");*/ return; }
	if(reqobj.request.readyState == 4) this.DocumentLoaded(index);
}


DataDocument.TryNextDocument = function() {
	if(this.State.Active.length >= DD_MAX_CONCURRENT_REQUESTS) return;
	if(this.State.Pending.length == 0) return;

	var idx = this.State.Pending.pop(); // use shift() for FIFO and pop() for FILO
	this.State.Active.push(idx);

	var reqobj = this.State.Items[idx];
	reqobj.request.open(reqobj.method, reqobj.fullURI, reqobj.async);
	if(reqobj.mimeType) reqobj.request.setRequestHeader("Content-Type", reqobj.mimeType);
	reqobj.request.send(reqobj.postData);
}


DataDocument.CancelLoad = function(index) { 
	// -- nuke if active
	var ai = -1;
	for(var ae=0; ae<this.State.Active.length; ae++)
		if(this.State.Active[ae] == index) ai = ae;
	if(ai>-1) {
		if(this.State.Items[ai] != null) {
			var xhr = this.State.Items[ai].request;
			if(xhr && ((xhr.readyState & 3) != 0)) xhr.abort();
		}
		this.State.Active.splice(ai, 1);
	}

	// -- remove from pending list
	var pi = -1;
	for(var pe=0; pe<this.State.Pending.length; pe++)
		if(this.State.Pending[pe] == index) pi = pe;
	if(pi>-1) this.State.Pending.splice(pi, 1);

	// -- delete request object
	this.State.Items[index] = null;
}


DataDocument.LoadFromServer = function(path, extType, asyncCompleteHandler, dataToSend, dataMimeType) {
	if(" xml text ".indexOf(" " + extType.toLowerCase() + " ") < 0)
		throw "DataDocument.LoadFromServer: unrecognized external data type (" + extType + ")";

	var xhr = CreateXMLHttpRequest();
	var uri = path;
	var method = (dataToSend != null) ? "POST" : "GET";
	dataToSend = dataToSend || "";

	// -- avert disaster
	if(xhr == null) {
		if(asyncCompleteHandler)
			asyncCompleteHandler(null, null, false);
		else
			return { request: null, data: null, ref: -1, OK: false };
	}

	if(!uri.match(/^http(s)?\:/)) {
		var qmark = location.href.indexOf("?");
		var idx = location.href.lastIndexOf("/");
		if(idx > qmark) idx = location.href.substr(0, qmark).lastIndexOf("/");

		uri = location.href.substr(0, idx + 1) + uri;
	}

	var async = !!asyncCompleteHandler;
	var reqidx = this.State.GetNextIndex();

	// Safari 1.3.0 and 2.0.0 over-aggressively cache, so we force a cache-miss by appending a unique parameter to the url...
	uri += ((uri.indexOf("?") < 0) ? "?" : "&") + "XFUID=" + (new Date().getTime());

	this.State.Items[reqidx] = {
		func: asyncCompleteHandler,
		fullURI: uri, request: xhr, dataType: extType, async: async,
		postData: dataToSend, method: method, mimeType: dataMimeType || null };

	if(async) {
		if(UA_SAFARI)
			xhr.onload = new Function("ABLFrame.DataDocument.DocumentLoaded(" + reqidx + ");");
		else
			xhr.onreadystatechange = new Function("DataDocument.OnReadyStateChange(" + reqidx + ");");
		this.State.Pending.push(reqidx);
		this.TryNextDocument();
		return { request: xhr, data: null, ref: reqidx };

	} else {
		xhr.open(method, uri, async);
		if(dataMimeType || null) xhr.setRequestHeader("Content-Type", dataMimeType);
		xhr.send(dataToSend);
		return this.DocumentLoaded(reqidx);
	}
}


DataDocument.LoadFromXMLSource = function(xmlSource) {
	var doc = null;

	if(window.DOMParser) {
		var dp = new DOMParser();
		doc = dp.parseFromString(xmlSource, "application/xml");
		if(doc && doc.documentElement && doc.documentElement.nodeName == "parsererror") doc = null;
	} else if(window.ActiveXObject) {
		doc = new ActiveXObject("Microsoft.XMLDOM");
		doc.async = false;
		if(! doc.loadXML(xmlSource)) doc = null;
	}

	return this.LoadFromXMLDocument(doc);
}


DataDocument.LoadFromXMLDocument = function(xmlDoc) {
	if((null == xmlDoc) || (null == xmlDoc.documentElement)) return null;

	function RecurseXMLNode(xmlNode, dataNode) {
		var curNode, i;

		curNode = new DataNode(xmlNode.nodeName, "", dataNode, 0);
		for(i=0; i<xmlNode.attributes.length; i++)
			new DataNode(xmlNode.attributes[i].nodeName, xmlNode.attributes[i].nodeValue, curNode, DN_ISATTR);

		for(i=0; i<xmlNode.childNodes.length; i++) {
			var xn = xmlNode.childNodes[i];
			switch(xn.nodeType) {
				case 1: // tag node
					RecurseXMLNode(xn, curNode);
					break;

				case 3: // text node
					var tx = xn.nodeValue.trim();
					curNode._val = (curNode._val + " " + tx).trim();
					new DataNode("__text", tx, curNode, DN_ISTEXT);
					break;

				default: break;
			}
		}

		return curNode;
	}

	return RecurseXMLNode(xmlDoc.documentElement, null);
}



/* ================ DataNode == */

function DataNode(name, val, parent, flags) {
	this._name = name;
	this._val = val || "";
	this._flags = flags || 0;
	this._parent = parent || null;

	// hierarchical access
	this._sublist = [];
	if(this._parent) this._parent._sublist.push(this);

	// direct access, non-text nodes only
	if(this._parent && ((this._flags & DN_ISTEXT) == 0)) {
		var n = this._parent[this._name];
		if(n == null)
			this._parent[this._name] = this;
		else {
			if(IsObject(n)) {
				this._parent[this._name] = [];
				this._parent[this._name].push(n);
			}
			this._parent[this._name].push(this);
		}
	}
}


DataNode.prototype = {
	toString: function() { return this._val; },

	SerializeToXML: function() {
		var xml = "";

		if(this._flags != 0) throw "DataNode.SerializeToXML: can only serialize full nodes.";

		function RecurseNode(dataNode) {
			var intag = false;

			xml += " <" + dataNode._name;

			for(var i=0; i<dataNode._sublist.length; i++) {
				var subNode = dataNode._sublist[i];
				if(subNode._flags & DN_ISATTR)
					xml += " " + subNode._name + "=\"" + subNode._val + "\"";
				else {
					if(!intag) { xml += ">"; intag = true; }
					if(subNode._flags & DN_ISTEXT)
						xml += subNode._val;
					else
						RecurseNode(subNode);
				}
			}

			if(intag)
				xml += "<\/" + dataNode._name + "> ";
			else
				xml += " \/> ";
		}

		RecurseNode(this);
		return xml;
	}
};



function ApplyTemplate(data, template) {
	var condin = -1, condend = -1, t, req, reqok, fval, mustEncode;

	// first process out all the conditionals
	while((condin = template.indexOf("[[")) > -1) {
		t = template.indexOf(" ", condin);
		condend = template.indexOf("]]", t);
		if(t == -1 || condend == -1) break; // bad template, no cookie
		req = Trim(template.substring(condin + 2, t));
		if(req.charAt(0) == "!") {
			req = req.substr(1);
			reqok = !data[req] || !data[req]._val.length;
		} else
			reqok = data[req] && data[req]._val.length;
		if(reqok) // if (!)reqfield - delete entire section of template
			template = template.substring(0, condin) + template.substring(t + 1, condend) + template.substr(condend + 2);
		else // else just delete conditional syntax
			template = template.substring(0, condin) + template.substr(condend + 2);
	}
	
	// next replace the %% field content holders
	while((condin = template.indexOf("%%")) > -1) {
		condend = template.indexOf("%%", condin+2);
		req = Trim(template.substring(condin + 2, condend));
		if(!req.length) break; // whatever

		mustEncode = (req.charAt(0) == "@");
		req = req.replace("@", "");
		fval = ((data[req]) ? data[req]._val : "NOT FOUND");
		if(mustEncode) fval = encodeURIComponent(fval);

		template = template.substring(0, condin) + fval + template.substr(condend + 2);
	}
	
	// done
	return template;
}


/**
 * Creates a hidden iframe and opens the url in it.
 * 
 * @param {Object} url The url to be opened in the iframe
 * @param {Object} callback The optional callback function. Will be executed when the url in the iframe is loaded.
 */
function TouchExternalUrl(url, callback) {
	
	if (!url || !url.length) return;
	if (!callback) callback = function() {};
	
	var ifr = document.createElement("IFRAME");
	ifr.src = url;
	ifr.style.display = "none";
	document.body.appendChild(ifr);

    if (ifr.addEventListener)
		ifr.addEventListener("load", callback, true);
    else
		ifr.attachEvent("onload", callback); // IE

}


/**
 * Loads an external script dynamically.
 * @param {Object} src Url of script
 * @param {Object} onComplete function to be executed on complete
 * @param {Object} onFail function to be executed on fail (firefox only)
 */
function loadScript(src, onComplete, onFail) {
	if ( !src || !src.length ) return;
	if ( !onComplete ) onComplete = function() {};
	if ( !onFail ) onFail = function() {};
	
	var timers = {};
	
	var se = document.createElement('script');
	se.setAttribute('type', 'text/javascript');
	se.setAttribute('src', src);
	if ( UA_MSIE ) {
		se.onreadystatechange = function() {
			if ( this.readyState.match(/loaded|complete/) ) {
				onComplete();
			}
		}
	}
	else {
		se.onload = onComplete; // Firefox & Safari
		se.onerror = onFail; // Firefox only
	}
	
	// ARGH!! need a hack to support Safari 2
	if (UA_SAFARI && !navigator.userAgent.match(/Version\/3/)) {
		timers[src] = setInterval(function() {
			if (/loaded|complete/.test(document.readyState)) {
				clearInterval(timers[src]);
				onComplete();
			}			
		}, 50);
	}
	
	
	
	document.getElementsByTagName('head')[0].appendChild(se);
}

// -- set flag that we have loaded
window.XF_DATA_LOADED = true;
