/**
 *	XMLhttp (send and) load
 *	-----------------------------------
 */

var XMLHttp = function(){

	var HEADER_ACCEPT		= 'text/xml,application/xml',
		HEADER_CONTENT_TYPE = 'application/x-www-form-urlencoded',
		HEADER_CONNECTION	= 'close',

		CONNECTION_TIMEOUT = 10000;

	function Connection(settings) {
		var set = settings || {};
		this.acceptHeader  = set.acceptHeader || HEADER_ACCEPT;
		this.contentType = set.contentType || HEADER_CONTENT_TYPE;
		this.connection = set.connection || HEADER_CONNECTION;
		if(set.getResponse) {
			this.getResponse = set.getResponse;
		}
	}

	Connection.prototype = {
		load:function(url, func, scope){ 
			return this.sendAndLoad(null, url, func, scope, 'get'); 
		},

		sendAndLoad:function(doc, url, func, scope, type){
			var xmlhttp = getXMLHttpRequest();
			var async = func? true:false; 
			var method = type || 'post';

			xmlhttp.open(method, url, async);
			if(async) {
				var self = this;
				var connectionTimeout = setTimeout(function(){
					self.abort(xmlhttp);
					func.call(scope || window, null, 408);
				}, CONNECTION_TIMEOUT);

				xmlhttp.onreadystatechange = function() {
					if(xmlhttp.readyState == 4)	{
						clearTimeout(connectionTimeout);
						var xml = xmlhttp.responseXML;
						if(xml && xml.documentElement && /parser?error/i.test(xml.documentElement.nodeName)) {
							throw Error('Parser error!');
						} else {
							func.call(scope || window, self.getResponse(xmlhttp), xmlhttp.status);
						}
					}
				}
			}
			
			xmlhttp.setRequestHeader('Accept', this.acceptHeader);
			xmlhttp.setRequestHeader('Content-Type', this.contentType);
			xmlhttp.setRequestHeader('Connection', this.connection);
			xmlhttp.setRequestHeader('Referer', document.location.href);
			xmlhttp.setRequestHeader('User-Agent', navigator.userAgent);
			
			doc && xmlhttp.setRequestHeader('Content-length', doc.length);
			xmlhttp.send(doc);

			return async? xmlhttp : this.getResponse(xmlhttp);
		},

		getResponse:function(xmlhttp) {
			return xmlhttp.responseXML || xmlhttp.responseText;
		},

		abort:function(xmlhttp) {
			try {
				xmlhttp.onreadystatechange = null;
				xmlhttp.abort();
			} catch (e) {}
		},

		sendForm:function(form, origin, func, scope) {
			var post = Connection.getFormValues(form, origin);
			return this.sendAndLoad(post, form.getAttribute('action'), func, scope);
		}
	}

	function getXMLHttpRequest() {
		if(window.XMLHttpRequest) {
			return new XMLHttpRequest();
		} else if(window.ActiveXObject) {
			var xmlhttp, http = ['Microsoft.XMLHTTP', 'Msxml2.XMLHTTP'], l = http.length;
			while(l--) {
				try {
					xmlhttp = new ActiveXObject(http[l]);
					return xmlhttp;
				} catch (e) {}
			}
		} else return false;
	}
	
	/**
	 *	Instance factory
	 *	--------------------------
	 */

	Connection.getInstance = function(type) {
		switch (type) {
			case 'HTMLLoader':
				return new Connection({
					acceptHeader: 'text/html', 
					getResponse: function(xmlhttp){
						var dom = document.createElement('div');
							dom.innerHTML = xmlhttp.responseText;
						var l = dom.childNodes.length -1;
						var fragment = document.createDocumentFragment();
						for(var child,i=l; i>=0; i--) {
							fragment.insertBefore(dom.childNodes[i], fragment.firstChild);
						}
						return fragment;
					}
				});
			case 'JSLoader':
				return new Connection({
					acceptHeader: 'application/x-javascript,text/javascript',
					getResponse:function(xmlhttp){
						var source = xmlhttp.responseText;
						return function(scope){
							if(scope) {
								var handler = new Function(source);
								handler.apply(scope, arguments);
							} else {
								window.eval(source);
							}
						}
					}
				});
			case 'JSONLoader':
				return new Connection({
					acceptHeader: 'application/json,application/x-javascript,text/javascript,text/plain,*/*',
					getResponse: function(xmlhttp){
						return eval('('+xmlhttp.responseText+')');
					}
				});
			case 'XMLLoader': default:
				return new Connection();
		}
	}

	/**
	 *	Form values helper
	 *	--------------------------
	 */

	var TYPE_TEXT	= 'text',
		TYPE_AREA	= 'textarea',
		TYPE_HIDDEN = 'hidden',
		TYPE_CHECK	= 'checkbox',
		TYPE_RADIO	= 'radio',
		TYPE_SELECT	= 'select-one',
		TYPE_MULTI	= 'select-multiple',
		TYPE_BUTTON	= 'button',
		TYPE_SUBMIT	= 'submit',
		TYPE_IMAGE  = 'image',
	
		ATTR_CHECKED = 'checked',
		ATTR_SELECTED = 'selected';

	Connection.getFormValues = function(form, origin) {
		var post = [], equals = '=', chosen = /(checked|true|selected)/i;
		function addPostValue(name, value) {
			post.push(encodeURIComponent(name) + equals + encodeURIComponent(value));
		}
		for(var element,i=0; element=form.elements[i++];) {
			switch (element.type.toLowerCase()) {
				case TYPE_TEXT: case TYPE_AREA: case TYPE_HIDDEN:
					addPostValue(element.name, element.value);
				break;
				case TYPE_CHECK: case TYPE_RADIO:
					if(element.checked || chosen.test(element.getAttribute(ATTR_CHECKED))) {
						addPostValue(element.name, element.value);
					}
				break;
				case TYPE_SELECT:
					if(element.selectedIndex >= 0) {
						var option = element.options[element.selectedIndex];
						addPostValue(element.name, (option.value || option.text));
					}
				break;
				case TYPE_MULTI:
					for(var option,j=0; option=element.options[j++];) {
						if(option.selected || chosen.test(option.getAttribute(ATTR_SELECTED))) {
							addPostValue(element.name, (option.value || option.text));
						}
					}
				break;
				case TYPE_SUBMIT: case TYPE_BUTTON: case TYPE_IMAGE: 
					if(element == origin && element.name) {
						addPostValue(element.name, element.value);
					}
				break;
			}
		}

		return post.join('&');
	}
	
	// returned class
	return Connection;
}();