Function.prototype.bind = function(object)
{
	var method = this;
	return function () { method.apply(object, arguments); };
}

function isset(v) { return (typeof v == "undefined") ? false : true; }

/*!\brief	Calls callback function for each member of the array with an
 *		optional data variable.
 * \param	a		Array
 * \param	cb		callback
 * \param	d		data (optional)
 * \return	data (optional)
 */
function arrForeach(a, cb, d)
{
	for (var i=0; i<a.length; i++) d = cb(a[i], d);
	return d;
}

/*!\brief	Reduces Array by removing [0] only elements.  Accepts an
 *		optional max depth parameter.
 * \param	arr		Array
 * \param	d		max depth (optional)
 * \return	Array
 */
function arrRdc(arr, d)
{
	var			i;

	if (typeof d == "undefined") d = -1;

	if (!d) return arr;
	else if (d > 0) d--;

	if (arr.constructor != Array) return arr;
	if (arr.length == 1) return arr = arrRdc(arr[0], d);

	for (i in arr) arr[i] = arrRdc(arr[i], d);
	return arr;
}

/*!\brief	Converts Array to a String.
 * \param	arr		Array
 * \param	d		depth (internal)
 * \return	String
 */
function arrToStr(arr, d)
{
	var			i, j, s = "";

	if (typeof d == "undefined") d = 0;

	if (arr.constructor == String) return "\"" + arr + "\"\n";

	for (i in arr) {
		for (j=0; j<d; j++) s += ".       ";
		s += "[" + i + "]";
		if (typeof arr[i] == "object")
			s += "\n" + arrToStr(arr[i], d+1);
		else s += " \"" + arr[i] + "\"\n";
	}

	return s;
}

/*!\brief	Class that implements the API layer for BSApi calls. */
function BSApi()
{
	var			cb = null, x = null;

	try { x = new ActiveXObject("Msxml2.XMLHTTP"); }
	catch (e) {
		try { x = new ActiveXObject("Microsoft.XMLHTTP"); }
		catch (e2) { }
	}

	if (!x && typeof XMLHttpRequest != "undefined")
		x = new XMLHttpRequest();

	if (!x) throw "missing XMLHttpRequest";

	this.xmlhttp = x;

	/*!\brief	Converts DOM object to an Array.
	 * \param	dn		DOM node
	 * \param	a		Array
	 * \return	Array
	 */
	this.domToArr = function(dn, a)
	{
		var			i, t;

		if (typeof a == "undefined") a = new Array();

		for (dn=dn.firstChild, i=0; dn; dn = dn.nextSibling) {
			switch (dn.nodeType) {
			case 1: // XML_ELEMENT_NODE
				if (!isset(a[dn.nodeName])) {
					a[dn.nodeName] = new Array();
					i = 0;
				}
				else for (i=0; isset(a[dn.nodeName][i]); i++);

				a[dn.nodeName][i] = new Array();

				if (dn.hasChildNodes())
					this.domToArr(dn, a[dn.nodeName][i]);

				break;

			case 3: // XML_TEXT_NODE
				a[i] = dn.nodeValue;
				break;
			}
		}
	}

	/*!\brief	Default callback function.
	 * \param	arr		Array
	 */
	this.arrToStrCB = function(arr)
	{
		var s = "<pre>Bsapi::arrToStrCB()\n" + this.arrToStr(arr);
		if (typeof arr["response"]["success"] == "undefined")
			s += "\n*** ERROR detected ***\n" +
				this.arrToStr(arr["response"]);
		document.write(s);
	}

	/*!\brief	Parse callback function to process returned DOM. */
	this.parseCB = function()
	{
		if (this.xmlhttp.readyState != 4) return;

		var			arr = new Array(), i, tA;

		if (!this.xmlhttp.responseXML) throw "XML parse error";

		this.domToArr(this.xmlhttp.responseXML, arr);

		if (typeof arr["BitShelterAPI"] == "undefined")
			throw "BitShelterAPI parse error";

		arr = arr["BitShelterAPI"][0];

		arr["identity"] = arrRdc(arr["identity"]);

		tA = arr["response"] = arr["response"][0];
		if (typeof tA["error"] != "undefined") {
			for (i=0, tA=tA["error"]; i<tA.length; i++)
				tA[i] = arrRdc(tA[i]);
		}
		else if (typeof tA["success"] != "undefined")
			tA["success"] = arrRdc(tA["success"]);

		if (typeof arr["return"] != "undefined")
			arr["return"] = arr["return"][0];

		if (typeof arr["session"] != "undefined")
			arr["session"] = arrRdc(arr["session"]);

		if (typeof arr["usage"] != "undefined")
			arr["usage"] = arrRdc(arr["usage"]);

		this.cb(arr);
		this.cb = null;
	}

	/*!\brief	Open connection to BSApi and call optional callback.
	 * \param	url		URL
	 * \param	cb		callback function (optional)
	 */
	this.open = function(url, cb)
	{
		if (typeof cb == "undefined") this.cb = this.arrToStrCB;
		else this.cb = cb;

		this.xmlhttp.open("GET", url, true);
		this.xmlhttp.onreadystatechange = this.parseCB.bind(this);
		this.xmlhttp.send(null);
	}

	this.responseParse = function(data)
	{
		if (isset(data['response']['success']) && isset(data['return']))
			return data['return'];
		else if (isset(data['response']['error'])) {
			var str = '';
			throw arrForeach(data['response']['error'], function(a, s) { if (s != '') s += '\n'; return s + a['message']; }, str);
		}
		else throw 'invalid response';
	}

}
