// Silverajax by Jean-Nicolas Jolivet
// A simple cross-browser XHMLHttpRequest wrapper.
// Licensed under the MIT License

// IE 4 & 5 don't implement Function.apply( ).
// This workaround is based on code by Aaron Boodman.

if (!Function.prototype.apply) {
    // Invoke this function as a method of the specified object,
    // passing the specified parameters.  We have to use eval( ) to do this
    Function.prototype.apply = function (object, parameters) {
        var f = this;                // The function to invoke
        var o = object || window;    // The object to invoke it on
        var args = parameters || []; // The arguments to pass
        o._$_apply_$_ = f;
        var stringArgs = [];
        for(var i = 0; i < args.length; i++)
            stringArgs[i] = "args[" + i + "]";
        var arglist = stringArgs.join(",");
        var methodcall = "o._$_apply_$_(" + arglist + ");";
        var result = eval(methodcall);
        delete o._$_apply_$_;
        return result;
    };
}

// Silverajax constructor
// Arguments:
//   url: Where to send that request
//   options: a bunch of useful options
var Silverajax = function (url, options) {
	
	// define the default options
	this.options =  {
		timeout: 10000,				// The request timeout in milliseconds
		expectJson: false,			// Wheter or not to eval the json response
		expectJavascript: false,	// Whether or not to eval the response 
		method: 'GET',				// The request method ('POST' or 'GET')
		update: '', 				// An element to update with the responseText
		encoding: 'utf-8',			// The encoding to use for POST requests
		headers: {
			'X-Requested-With': 'XMLHttpRequest',
			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
		}
	};
	this.url = url;
	
	// Browser detection
	if (window.XMLHttpRequest) {
		this.request = new XMLHttpRequest(); // nice, a decent browser
	}
	else if (window.ActiveXObject) { // Some version of IE...
		var ua = navigator.userAgent.toLowerCase();
		if (ua.indexOf('msie 5') === -1) {
    		this.request = new ActiveXObject("Msxml2.XMLHTTP");
		} else {
			this.request = new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
	
	this.options = this.mergeObjects(this.options, options);
	
	// Set the request timeout based on the options
	this.requestTimeout = setTimeout(this.requestTimedOut, this.options.timeout);
	// Make sure the method is eighter POST or GET, else, default to GET
	this.options.method = (this.options.method === 'POST' || this.options.method === 'GET') ? this.options.method : 'GET';
};

// Method: initOptions
// Merges this.options with the options passed in the arguments
Silverajax.prototype.mergeObjects = function (orig, ext) {
	var name;
	// I searched for a better way to do this, (i.e. merge objects)
	// couldn't find anything so I decided to do it that way... it
	// might not be the best way but it works well...
	for (name in ext || {}) {
		if (typeof name !== "function") {
			orig[name] = ext[name];
		}
	}
	return orig;
};

// Method: handleRequest
// The callback for onreadystatechange
Silverajax.prototype.handleRequest = function () {
    if (this.request.readyState === 4) {  // If the request is finished
    	clearTimeout(this.requestTimeout); // Clear our timeout
        this.completed(); // Fire onComplete
    }
    
};

// Method: requestTimedOut
// Called when the request has timed-out
Silverajax.prototype.requestTimedOut = function () {
	this.abort();
	this.onTimeout();
};

// Method: onComplete
// Called when the XMLHttpRequest is complete
Silverajax.prototype.completed = function () {
	this.response = {
		status: this.request.status,
		statusText: this.request.statusText,
		responseText: this.request.responseText,
		responseXml: this.request.responseXML,
		responseJson: {}
	};
	if (typeof this.onComplete === 'function') { this.onComplete(this.response);}
	if (this.request.status === 200) {
		if(this.options.update) {
			var el = typeof this.options.update === 'string' ? document.getElementById(this.options.update) : this.options.update;
			el.innerHTML = this.request.responseText;
		}
		if(this.options.expectJson) {
			this.response.responseJson = !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(this.request.responseText.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + this.request.responseText + ')');
		}
		if(this.options.expectJavascript) {
			eval(this.request.responseText);
		}
		if (typeof this.onSuccess === 'function') {this.onSuccess(this.response);}
    }
    else {
    	if (typeof this.onFailure === 'function') {this.onFailure(this.response);}
    }
};

// Method: send
// Will send the XMLHttpRequest
Silverajax.prototype.send = function (sendData) {
	var that = this;
	var data = sendData || null;
	var headers = {};
	// Add the callback for the onreadystatechange event
	this.request.onreadystatechange = function () {
		that.handleRequest.apply(that);	
	};
	// Initialize the request based on our options...
	this.url = this.options.method === "GET" && data ? this.url + "?" + data : this.url;
	this.request.open(this.options.method, this.url, true);
	
	if(this.options.method == 'POST' && data) {
		headers = {
			"Content-type": "application/x-www-form-urlencoded; charset=" + this.options.encoding,
			"Content-length": data.length,
			"Connection": "close"
		};
	}
	// Merge the options'headers with the local headers object and set them
	headers = this.mergeObjects(this.options.headers, headers);
	this.setHeaders(headers);
	// Send the request
	this.request.send(data);

};

// Method: addHeader(key, value)
// Adds a new header to the current headers
Silverajax.prototype.addHeader = function (key, value) {
	this.options.headers = this.mergeObjects(this.options.headers, {key: value});
};

// Method: deleteHeader(key)
// Deletes the header at [key]
Silverajax.prototype.deleteHeader = function (key) {
	delete this.options.headers[key];
};

// Method: setHeaders
// Used internally to set the request Headers..
Silverajax.prototype.setHeaders = function (headers) {
	var name;
	for(name in headers) {
		if(typeof headers[name] !== 'function') {
			this.request.setRequestHeader(name, headers[name]);
		}
	}
};

// Method: abort
// Aborts the current XMLHttpRequest
Silverajax.prototype.abort = function () {
	this.request.abort();
};