var ajax =
{
	'support': true,
	'ajax': false,
	'inProgress': [ false, false ],
	'queue': [ ],
	'callback': ['', ''],
	'timer': [0, 0],
	'passthruData': [ null, null ],
	'volgnummer': 0,
	'startTime': [0, 0],
	'log': [ ],
	'url': ['', ''],

	/**
	 * Registreert de callbackfunctie en start de verzending. Als er nog een
	 * oude ajaxrequest loopt, wordt de nieuwe even in de wacht gezet tot-ie
	 * aan de beurt is.
	 *
	 * Parameters:
	 * @param string url         // Het webadres waarheen de request moet worden gedaan
	 * @param string data        // De gegevens die meegestuurd moeten worden
	 * @param function callback  // Reference naar de functie die de respons moet verwerken
	 * @param string method      // POST, GET of ERROR; default GET. ERROR is ook POST, maar wordt niet gelogd.
	 * @param number timeout     // Maximum aantal seconden wachttijd op antwoord van de server; default 4
	 * @param string contentType // Content mime-type voor POST request; default 'application/x-www-form-urlencoded'
	 */
	'send': function(url, data, callback, method, timeout, contentType, passthruData, volgnummer)
	{
		if (!this.support)
			return false;

		while (this.log.length > 100)
			this.log.shift();

		if (typeof volgnummer == 'undefined')
			volgnummer = ++this.volgnummer;

		var startTime = new Date();

		var channel = 0;
		if (this.inProgress[0])
		{
			/**
			 * Als de onderstaande uitgecommentarieerde stukjes aanzet, kan het systeem
			 * twee ajaxrequests parallel doen en zal een trage request de rest niet
			 * tegenhouden. Alleen Firefox kan er niet mee overweg en gaat allerlei
			 * rare dingen doen (dezelfde request met dezelfde callbackfunctie twee
			 * keer opvragen, waarbij die de tweede keer gecached is, zodat de request
			 * time-outed en er helemaal geen resultaat terugkomt, en meer van dat
			 * soort onzin). Safari en IE 7 doen het wel goed. IE 6 nog niet getest.
			 */
			channel = 1;
			if (this.inProgress[1])
			{
				this.queue.push({'url': url, 'data': data, 'callback': callback, 'method': method, 'timeout': timeout, 'contentType': contentType, 'passthruData': passthruData, 'volgnummer': volgnummer});
				this.log.push({ 'what': 'queue #'+volgnummer, 'time': startTime.valueOf()+' ('+startTime.toLocaleTimeString()+')', 'url': url, 'data': method == 'ERROR' ? data.replace(/^.*?msg=([^&]*)&.*$/, function(ref0, ref1) { return decodeURIComponent(ref1); }) : data });
				return volgnummer;
			}
		}

		this.inProgress[channel] = volgnummer;
		this.url[channel] = url;
		this.startTime[channel] = startTime;
		this.log.push({ 'what': 'send #'+volgnummer+' (channel '+channel+')', 'time': startTime.valueOf()+' ('+startTime.toLocaleTimeString()+')', 'url': url, 'data': method == 'ERROR' ? data.replace(/^.*?msg=([^&]*)&.*$/, function(ref0, ref1) { return decodeURIComponent(ref1); }) : data });

		if (method == 'ERROR')
			method = 'POST';
		method = (!method || method.search(/^(post|get)$/i) == -1) ? 'GET' : method.toUpperCase();
		data = data || null;
		timeout = timeout || 90;

		this.timer[channel] = setTimeout(function() { return ajax.timeout(channel); }, timeout * 1000);
		this.callback[channel] = callback;
		this.passthruData[channel] = passthruData;
		this.ajax[channel].open(method, url, true);
		this.ajax[channel].onreadystatechange = function() { return ajax.receive(channel); }
		if (method == 'POST')
			this.ajax[channel].setRequestHeader('Content-Type', !contentType ? 'application/x-www-form-urlencoded' : contentType);
		this.ajax[channel].send(data);

		return volgnummer;
	},

	/**
	 * Handelt de ontvangst van de respons af en geeft de ontvangen gegevens door
	 * aan de geregistreerde callbackfunctie.
	 *
	 * Als er nog opdrachten in de wacht staan, wordt de eerstvolgende daarvan
	 * opgepakt.
	 */
	'receive': function(channel)
	{
		if (this.ajax[channel].readyState != 4)
			return false;

		if (this.ajax[channel].status != 200)
			return false;

		clearTimeout(this.timer[channel]);

		if (!this.ajax[channel].responseText)
			return false;

		var contentType = this.ajax[channel].getResponseHeader('Content-Type');
		if (contentType && contentType.match(/\bjson\b/i))
		{
			try { var o = eval('('+this.ajax[channel].responseText+')'); }
			catch(err) { o = this.ajax[channel].responseText; }
		}
		else
			o = this.ajax[channel].responseText;

		var callback = this.callback[channel];
		var passthruData = this.passthruData[channel];
		var url = this.url[channel];
		this.callback[channel] = '';
		this.passthruData[channel] = null;
		this.url[channel] = '';

		var endTime = new Date();
		this.log.push({ 'what': 'receive #'+this.inProgress[channel]+' (channel '+channel+')', 'time': endTime.valueOf()+' (request took '+(endTime - this.startTime[channel])+' ms)', 'url': url });
		this.inProgress[channel] = false;

		if (this.queue.length != 0)
		{
			var data = this.queue.shift();
			ajax.send(data.url, data.data, data.callback, data.method, data.timeout, data.contentType, data.passthruData, data.volgnummer);
		}

		if (callback)
			callback(o, passthruData, 'ok');

		return true;
	},

	/**
	 * Zorgt ervoor dat de request afgehandeld wordt als er na de
	 * timeout-tijd nog geen antwoord is ontvangen. De request wordt
	 * afgebroken, de callbackfunctie wordt aangeroepen zonder gegevens,
	 * en als er nog opdrachten in de wacht staan, wordt de eerstevolgende
	 * daarvan opgepakt.
	 */
	'timeout': function(channel, abort)
	{
		this.ajax[channel].abort();

		var callback = this.callback[channel];
		var passthruData = this.passthruData[channel];
		var url = this.url[channel];
		this.callback[channel] = '';
		this.passthruData[channel] = null;
		this.url[channel] = '';

		var endTime = new Date();
		this.log.push({ 'what': (abort ? 'abort' : 'timeout')+' #'+this.inProgress[channel]+' (channel '+channel+')', 'time': endTime.valueOf()+' ('+(abort ? 'aborted' : 'timed out')+' after '+(endTime - this.startTime[channel])+' ms)', 'url': url });
		this.inProgress[channel] = false;

		if (this.queue.length != 0)
		{
			data = this.queue.shift();
			ajax.send(data.url, data.data, data.callback, data.method, data.timeout, data.contentType, data.passthruData, data.volgnummer);
		}

		if (callback)
			callback(null, passthruData, abort ? 'abort' : 'timeout');

		return true;
	},

	/**
	 * Kapt een request af net zoals de timeout functie, maar heeft als invoer
	 * een volgnummer nodig. Als de request nog in de queue zit, wordt-ie
	 * daaruit en wordt de callback functie aangeroepen op dezelfde manier
	 * alsof er een timeout is geweest, dus zonder resultaatgegevens.
	 */
	'abort': function(volgnummer)
	{
		var channel = false;
		if (this.inProgress[0] === volgnummer)
			channel = 0;
		else if (this.inProgress[1] === volgnummer)
			channel = 1;

		if (channel !== false)
		{
			clearTimeout(this.timer[channel]);
			return this.timeout(channel, true);
		}

		var index = 0;
		while (index < this.queue.length && this.queue[index].volgnummer !== volgnummer)
			index++;

		if (index >= this.queue.length) // dan is-ie niet gevonden
			return false;

		this.log.push({ 'what': 'abort #'+volgnummer+' before sending', 'time': new Date().valueOf(), 'url': this.queue[index].url, 'data': this.queue[index].data });

		var callback = this.queue[index].callback;
		var passthruData = this.queue[index].passthruData;
		this.queue.splice(index, 1);

		if (callback)
			callback(null, passthruData, 'abort');

		return true;
	}
};


// Controleer of de browser wel ajax support biedt en laadt het ajax object.
if (window.XMLHttpRequest) // most browsers
	ajax.ajax = [ new XMLHttpRequest(), new XMLHttpRequest() ];

else if (window.ActiveXObject) // IE
{
	try
	{
		ajax.ajax = [ new ActiveXObject("Msxml2.XMLHTTP"), new ActiveXObject("Msxml2.XMLHTTP") ];
	}
	catch (err)
	{
		try
		{
			ajax.ajax = [ new ActiveXObject("Microsoft.XMLHTTP"), new ActiveXObject("Microsoft.XMLHTTP") ];
		}
		catch (err)
		{
			ajax.support = false;
		}
	}
}
else
	ajax.support = false;

