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


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

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 CanUseXMLHttpRequests() {
	if(window.XMLHttpRequest) return true;
	if(window.ActiveXObject) {
		try { var t = new ActiveXObject("Microsoft.XMLHTTP"); if(t==null) throw 1; }
		catch(e) { return false; }
		return true;
	}
	return false;
}

function CreateXMLHttpRequest() {
	if(window.XMLHttpRequest)
		return new XMLHttpRequest();
	else if(window.ActiveXObject)
		return new ActiveXObject("Microsoft.XMLHTTP");

	return null;
}


/* ================ 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



/* ================ 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);
	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) {
	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 || "";

	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 };

	if(async) {
		if(UA_SAFARI)
			xhr.onload = new Function("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);
		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) {
	// first process out all the conditionals
	var condin = -1, condend = -1, t, req, reqok;
	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
		template = template.substring(0, condin) + ((data[req]) ? data[req]._val : "NOT FOUND") + template.substr(condend + 2);
	}
	
	// done
	return template;
}
