// on the other side, there's the ajax sequencer.  need to define interface (again!!)
/*

ajax_transport.js

Assumes that external file to connect to will accept arguments in either get or post via standard HTTP
Assumes that external file will accept the following named parameters:
	'mode' -- main functionality directive
	'caller' -- text string reference for calling position (for callback function)
	all other parameters are optional.
Assumes that external file will return result in XML, formatted as:
	<?XML version="1.0"?>
	<Response>
		<Errors>
			<Error level="1">
				errors encountered during processing as individual items here
				preferably coded with a 'level' attribute indicating severity
			</Error>
		</Errors>
		<Mode>
			Mode requested for which this is a response
		</Mode>
		<Content>
			...response payload, if text...
		</Content>
		...other named parameters may appear here in more XML oriented payloads...
		<Caller>
			caller
		</Caller>
	</Response>
	NOTE: whitespace added here for clarity... final version should contain *NO* whitespace.
Assumes that all processing of payload is handled by client-side JS functions, responses should be 'dumb data'.
Assumes ALL communication may be asynchronous.  No 'wait' attempt is made unless enforced externally.

*/

function getXMLHTTP(){
	var A=null;
	try {
		A = new ActiveXObject("Msxml2.XMLHTTP");
	} catch(e) {
		try {
			A = new ActiveXObject("Microsoft.XMLHTTP");
		} catch(oc){
			A = null;
		}
	}
	if(!A && typeof XMLHttpRequest != "undefined") {
		A = new XMLHttpRequest();
	}
	return A;
}


function AjaxWorkUnit(url,mode,callback,caller,errorFunc) {
	this.url = url?url:'';
	this.mode = mode?mode:'';
	this.caller = caller?caller:'';
	this.callBack = callback?callback:null;
	this.errorFunction = errorFunc?errorFunc:null;
	this.ajaxunit = getXMLHTTP();
	
	// no worky for MSIE --
	// this.ajaxunit.workUnit = this;
	
	// should worky for MSIE, but will probably leak memory
	var selfer = this;
	// then function() { } wrap selfer.method to make accessible
	
	// public!
	this.postData = function(data) {
		this.ajaxunit.open("POST",this.url,true);
		this.ajaxunit.onreadystatechange = function() { selfer.processResponseGeneric(); }
			//alert('posting: '+this.checkData(data));
		this.ajaxunit.send(this.checkData(data));
	}
	this.getData = function(data) {
		this.ajaxunit.open("GET",this.url+'?'+this.checkData(data),true);
		this.ajaxunit.onreadystatechange = function() { selfer.processResponseGeneric(); }
		this.ajaxunit.send(null);
	}
	this.getNow = function(data) {
		this.ajaxunit.open("GET",this.url+'?'+this.checkData(data),false);
		this.ajaxunit.send(null);
		if(this.ajaxunit.readyState == '4') {
			return this.ajaxunit.responseXML;
		} else {
			return null;
		}
	}
	
	// internals!
	this.processResponseGeneric = function() {
		// generic response for asynchronous requests -- take the XML and route it through callback
		if(this.ajaxunit.readyState == '4') {
			// not even checking for 200 response... maybe later...
			var xmldata = this.ajaxunit.responseXML;
			var textdata = this.ajaxunit.responseText;
			//this.doError(textdata);
			if(typeof this.callBack == 'function') {
				this.callBack(xmldata);
			} else if(typeof document[this.mode + 'Response'] == 'function') {
				document[this.mode + 'Response'](xmldata);
			} else {
				this.doError('Call for mode '+this.mode+' returned without a callback function...\nDATA:\n'+xmldata);
			}
		}
	}
	this.processResponseLocal = function() {
		// generic response for asynchronous requests -- take the XML and route it through callback
		if(this.readyState == '4') {
			// not even checking for 200 response... maybe later...
			var xmldata = this.responseXML;
			if(typeof this.workUnit.callBack == 'function') {
				this.workUnit.callBack(this.ajaxunit.responseXML);
			} else if(typeof document[this.workUnit.mode + 'Response'] == 'function') {
				document[this.workUnit.mode + 'Response'](this.responseXML);
			} else {
				this.doError('Call for mode '+this.workUnit.mode+' returned without a callback function...\nDATA:\n'+this.responseXML);
			}
		}
	}
	this.isReady = function() {
		// if we're not doing anything, we're ready to do something else...
		if(this.ajaxunit.readyState == '0' || this.ajaxunit.readyState == '4') {
			return true;
		} else {
			return false;
		}
	}
	this.checkData = function(data) {
		// should be some better data validation here.. but this is a start.
		if(data == null) {
			// construct manually if null
			data = 'mode='+this.mode+'&caller='+this.caller;
		} else {
			// data better be a string
			if(typeof data != 'string') {
				this.doError('Data must be a string of format \'var1=val1&var2=val2\'! : '+data);
				return null;
			}
			// no spaces, obviously
			if(data.indexOf(' ') != -1) {
				this.doError('Data may not contain spaces! : '+data);
				return null;
			}
			// prepend the needed vars
			data = 'mode='+this.mode+'&caller='+this.caller+'&'+data;
		}
		// return the prepended string
		return data;
	}
	this.doError = function(error) {
		if(typeof this.errorFunction == 'function') {
			return this.errorFunction(error);
		} else {
			alert(error);
			return false;
		}
	}
}

function AJAXTransport(url) {
	this.url = url;
	this.acceptedModes = new Array();
	this.workUnits = new Array();
	this.errorFunction = arguments[1]?arguments[1]:null;
	this.initdata = arguments[2]?arguments[2]:null;
	this.decider = 'most definitely';
	this.HASAJAX = false;
	
	// public!
	this.getUnit = function(mode,callback,caller) {
		if(this.modeCheck(mode)) {
			return this.addUnit(mode,callback,caller);
		} else {
			return null;
		}
	}
	this.getBlankUnit = function(mode) {
		if(this.modeCheck(mode)) {
			return this.addUnit(mode,null,null);
		} else {
			return null;
		}
	}
	this.postRequest = function(mode,callback,caller,data) {
		if(this.modeCheck(mode)) {
			var workUnit = this.addUnit(mode,callback,caller);
			workUnit.postData(data);
		}
	}
	this.getRequest = function(mode,callback,caller,data) {
		if(this.modeCheck(mode)) {
			var workUnit = this.addUnit(mode,callback,caller);
			workUnit.getData(data);
		}
	}
	this.nowRequest = function(mode,data) {
		if(this.modeCheck(mode)) {
			var workUnit = this.addUnit(mode,null,null);
			return workUnit.getNow(data);
		} else {
			return null;
		}
	}
	
	// super internals
	this.addModer = function(moder) {
		this.acceptedModes[moder] = true;
	}
	this.modeCheck = function(mode) {
		if(!this.acceptedModes[mode]) {
			this.doError('Mode '+mode+' is not on the accepted list!');
			return false;
		} else {
			return true;
		}
	}
	this.addUnit = function(mode,callback,caller) {
		// look for unused/previously used workUnit
		for(var i = 0;i<this.workUnits.length;i++) {
			if(this.workUnits[i].isReady()) {
				// early out if we find one ready
				this.workUnits[i].mode = mode;
				this.workUnits[i].callBack = callback;
				this.workUnits[i].caller = caller;
				// bail! bail! bail! no unnecessary work!
				return this.workUnits[i];
			}
		}
		// otherwise, we have to add a new one
		var newWorkUnit = new AjaxWorkUnit(this.url,mode,callback,caller,this.errorFunction);
		this.workUnits[this.workUnits.length] = newWorkUnit;
		return newWorkUnit;
	}
	this.doError = function(error) {
		if(typeof this.errorFunction == 'function') {
			return this.errorFunction(error);
		} else {
			alert(error);
			return false;
		}
	}
	this.init = function() {
		// skips standard 'postRequest' since we don't have an accepted modes list yet
		// uses yet another closure since our 'this's are not yet correct...
		var selfer = this;
		var workUnit = this.addUnit(null,function() { return false; },null);
		var data = workUnit.getNow(this.initdata);
		if(data) {
			this.setAcceptedModes(data);
			this.HASAJAX = true;
		}
	}

	// standard callbacks
	this.setAcceptedModes = function(xmldata) {
		// silently sets accepted modes. no errors are reported (yet)
		var modesAvail = xmldata.getElementsByTagName('mode');
		for(var i=0;i<modesAvail.length;i++) {
			// this.doError("show me node type for this node: "+contents.childNodes[i].nodeType);
			// alert('should now be adding mode: '+modesAvail[i].firstChild.data+' for myself; decider being '+this.decider);
			this.addModer(modesAvail[i].firstChild.data);
		}
	}
	
	// run init when we create the transport to set available modes.
	this.init();
}
