// If IE is used, create a wrapper for the XMLHttpRequest object
if ( !window.XMLHttpRequest ) {
	XMLHttpRequest = function(){
		return new ActiveXObject("Microsoft.XMLHTTP");
	};
}
var HttpClient = IConfigurable.extend({
	constructor: function(url, config){
		var _this = this.constructor;
		this.url = url;
		this.xhr = null;
		this.onComplete = new Observer;
		this.onError = new Observer;
		this.onSend = new Observer;
		this.setConfig({
			method: 'POST',
			data: '',
			async: true,
			update: null,
			contentType: "application/x-www-form-urlencoded",
			maxReuse: 1
		});
		this.cache = {};
		var c = this.setConfig(config);
		c.data = _this.serialize(c.data);

		this.onComplete.add(Object.remove(c, 'onComplete'));
		this.onError.add(Object.remove(c, 'onError'));
		this.onSend.add(Object.remove(c, 'onSend'));

		if (c.id) _this.register(c.id, this);

	},
	request: function(data) {
		var _this = this.constructor;
		this.xhr = new XMLHttpRequest();
		var c = this.config;
		var xhr = this.xhr;
		if (data) c.data = _this.serialize(data);
		var _cacheItem = this.cache[c.data];
		if ( _cacheItem ) {
			this.xhr = _cacheItem.xhr;
			_cacheItem.uses += 1;
			this.cache[c.data] = _cacheItem;
			this._onStateChange();
			return;
		}
		
		if (c.method == 'GET') {
			this.url += ((this.url.indexOf("?") > -1) ? "&" : "?") + c.data;
			// IE likes to send both get and post data, prevent this
			c.data = '';
		}
		
		xhr.open(c.method, this.url, c.async);
		xhr.onreadystatechange = this._onStateChange.bind(this);
		
		xhr.setRequestHeader('Content-type', c.contentType);

		// Not necessary, but good to have
		xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
		xhr.setRequestHeader('Referer', location);
		
		// Make sure the browser sends the right content length
		if ( xhr.overrideMimeType )
			xhr.setRequestHeader("Connection", "close");
		var args = { cancel: false, data: c.data }
		this.onSend.fire(this, args);
		if ( !args.cancel ) {
			xhr.send(args.data);
			this.config.data = c.data;
		}
	},
	_onStateChange: function(){
		var xhr = this.xhr;
		if ( !xhr ) return;
		
		if (xhr.readyState == 4) { 
			if (xhr.status == 200 || xhr.status == 0 || xhr.status == 304){
				if (this.config.update) {
					DOM.get(this.config.update).innerHTML = xhr.responseText;
				}else {
					this.onComplete.fire(this, {xhr: xhr, xml: xhr.responseXML, text: xhr.responseText} );
				}
				var citem = this.cache[this.config.data];
				if (!citem) {
					this.cache[this.config.data] = { xhr:  xhr, uses: 1 };
				}
				else if (citem.uses >= this.config.maxReuse ) {
					delete this.cache[this.config.data];
				}
				delete xhr.onreadystatechange;
				this.xhr = null;
			}
			else {
				this.onError.fire(this, {xhr: xhr});
			}
		}
	}
}, {
	register: function(name, obj) {
		this[name] = function() {obj.request.apply(obj, arguments); };
		this[name].obj = obj;
	},
	
	serialize: function(obj) {
		if ( typeof obj == 'string' ) return obj;
		else if ( typeof obj == 'undefined') return '';
		var data = [];
		if ( obj.getElementsByTagName ) {
		['input','textarea','select'].forEach(function(tag) {
			forEach(DOM.getElementsByTagName(obj,tag), function(o) {
				var n, v;
				if (o.nodeName=='SELECT' ) { n = o.name; v = o.options[o.selectedIndex].value; }
				else if ((o.name && o.value && (/text|hidden|password/.test(o.type)|| typeof o.type=='undefined')) || ( /radio|checkbox/.test(o.type) && o.checked) ){ n = o.name; v = o.value; }
				if (n) data.push(encodeURIComponent(n)+'='+encodeURIComponent(v));
			});
		});
		}
		else
			forEach(obj, function(o,name) {
				if ( o ) {
					var val = o;
					if (o.constructor == Array) val = o.join(',');
					data.push(encodeURIComponent(name)+'='+encodeURIComponent(o));
				}
			});
		return data.join('&')
	}
});


var Tip = Base.extend(new function() {
	//private:
	var _delay = 300;
	var _xCord = 0;
	var _yCord = 0;
	var _timer = 0;
	var _close = 0;
	var _toolTip;
	var _targets;
	
	function _isValidTarget(o) {
		return _targets.some(function(obj) { return o === obj; });
	}
	
	return {
	//public:
		constructor: function(root,targets,opacity) {
			_toolTip = DOM.create('div', {
				className: 'toolTip',
				style: { position: 'absolute', visibility: 'hidden', opacity: opacity||.9 },
				children: [
					{ tag: 'b', attr: { className: 'ttTitle' } },
					{ tag: 'div', attr: { className: 'ttContent' } }
				]
			});
			_targets = [];
			this.root = root;
			this.title = _toolTip.firstChild;
			this.content = _toolTip.lastChild;
			this.onInit = new Observer;
			this.onShow = new Observer;
			Event.onDOMReady.add( this.init, this );
			if (targets) this.addTarget(targets);
			return this;
		},
		
		addTarget: function(a) {
			_targets = _targets.concat($A(a));
		},
		
		removeTarget: function(a) {
			_targets = _targets.filter( function(obj) { return a !== obj; } );
		},

		init: function() {
			Event.add(this.root,'mouseover', this.tipOver, this);
			document.body.appendChild(_toolTip);
			this.onInit.fire(this);
		},

		updateXY: function() {
			
			if ( BOM.is('ie') )
				return function(e) {
					_xCord = e.clientX+document.documentElement.scrollLeft;
					_yCord = e.clientY+document.documentElement.scrollTop;
				};
			else
				return function(e) {
					_xCord = e.pageX || e.clientX;
					_yCord = e.pageY || e.clientY;
				};
		}(),
		
		tipOut: function(e) {
			if (_timer) { clearTimeout(_timer); }
			_close = this.tipHide.delay(500, this, e.target||e.srcElement);			
		},
		
		tipHide: function(target) {
			Event.remove(target,'mouseout', this.tipOut);
			_toolTip.relatedElement = null;
			this.toggle();
		},
		
		tipOver: function(e) {
			if (_timer) { clearTimeout(_timer); }
			var t = e.target||e.srcElement;
			if (t && ( _isValidTarget(t) || _isValidTarget(t.nodeName)) ) {
				Event.stopPropagation(e);
				_timer = this.tipShow.delay(_delay, this, t);
				this.updateXY(e);
				
			}
		},
		
		tipShow: function(target) {
			Event.remove(target, 'mouseout', this.tipOut);
			Event.add(target, 'mouseout', this.tipOut, this);
			var tp = _yCord+15;
			var lt = _xCord+10;
			if ( parseInt(document.documentElement.clientWidth+document.documentElement.scrollLeft) < parseInt(_toolTip.offsetWidth+lt) ) {
				lt = parseInt(lt-(_toolTip.offsetWidth+10));
			} 
			if ( parseInt(document.documentElement.clientHeight+document.documentElement.scrollTop) < parseInt(_toolTip.offsetHeight+tp) ) {
				tp = parseInt(tp-(_toolTip.offsetHeight+10));
			}
			DOM.setStyle(_toolTip, {top: tp + 'px', left: lt+'px'});
			_toolTip.relatedElement = target;
			var sTip =  target.getAttribute('tip');
			if ( !sTip ) {
				sTip = target.title || target.alt;
				target.setAttribute('tip',sTip);
				target.title = target.alt = '';
			}
			_toolTip.firstChild.innerHTML = sTip;
			var args = Object.extract(this, 'title', 'content');
			args.target = target;
			this.onShow.fire(this, args);
			this.toggle(true);
		},
		
		toggle: function(on) {
			var v = on===true ? 'visible' : 'hidden';
			DOM.setStyle(_toolTip, 'visibility', v);
		}
		
	
	}

});

