/**
 * Frequently used DOM
 * 
 * a shortcut for document.getElementByID(). However, when a object is provided as an argument,
 * that object is returned unchanged.
 * @param string | object o
 * @return object
 */
jdom = function(o) {
	if ('object' == typeof(o)) {
		return o;
	} else {
		return document.getElementById(o);
	}
};
/**
 * A bit of cross-browser event hendling
 * Constructor.
 * @param object browserEvent
 */
jdom.event = function(browserEvent) {
	this.browserEvent = browserEvent || window.event;
	this.type = this.browserEvent.type;
	this.keyCode = this.browserEvent.keyCode;
	this.x = this.y = 0;
	if (this.browserEvent.pageX || this.browserEvent.pageY) {
		this.x = this.browserEvent.pageX;
		this.y = this.browserEvent.pageY;
	} else if (this.browserEvent.clientX || this.browserEvent.clientY) 	{
		this.x = this.browserEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
		this.y = this.browserEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;
	}
	this.element = this.browserEvent.srcElement || this.browserEvent.targetElement;
};

jdom.event.fire = function(el, type) {
	el = jdom(el);
	if (el.fireEvent)
	{
		el.fireEvent('on'+type)
	} else {
		var event = document.createEvent('HTMLEvents');
		event.initEvent(type, true, true);
		el.dispatchEvent(event);
	}
}

/**
 * jdom.event instance methods
 */
jdom.event.prototype = {
	/**
	 * Cross-browser stop
	 */
	stop: function() {
		this.e.cancelBubble = true;
		if (this.e.stopPropagation) this.e.stopPropagation();
	}
};

/**
 * Create event listener
 * @param string | object element
 * @param string type
 * @param function fn
 * @param object scope
 */
jdom.listen = function(element, type, fn, scope) {
	i = element;
	element = jdom(element);
	if (! element)
	{
		alert(element+": "+i);
		return;
	}
	scope = jdom(scope);
	var on = 'on' + type;
	var old = (element[on]) ? element[on] : function () {};
	element[on] = function (e) {
		e = new jdom.event(e);
		if ((false === old.call(window, e)) || (false === fn.call(scope || window, e))) return false;
		else return true;
	};
};
/**
 * Shortcut to jdom.listen(el, 'keyup', fn, scope)
 */
jdom.onKeyUp = function(el, key, fn, scope) {
	jdom.listen(el, 'keyup', 
		function(e) {
			if (key == e.keyCode) {
				fn.call(this, e);
			}
		}, scope);
};
/**
 * Shortcut to jdom.listen(window, 'load', fn, scope)
 */
jdom.onReady = function(fn, scope) {
	jdom.listen(window, 'load', fn, scope);
};
/**
 * Commonly used event key codes
 */
jdom.KEY_ESC = 27;
jdom.KEY_LEFT = 37;
jdom.KEY_UP = 38;
jdom.KEY_RIGHT = 39;
jdom.KEY_DOWN = 40;

/**
 * Commonly used functions.
 */
jdom.show = function(el) {
	jdom(el).style.display = 'block';
};
jdom.hide = function(el) {
	jdom(el).style.display = 'none';
};
jdom.toggle = function(el) {
	el = jdom(el);
	if (el.style.display != 'none') {
		jdom.hide(el);
		return false;
	} else {
		jdom.show(el);
		return true;
	}
};

/**
 * jdom.layer: absolutely positioned layers
 */
jdom.layer = function(el, show) {
	this.element = jdom(el);
	this.element.style.position = 'absolute';
	this.zIndex = null;
	// MSIE hack
	if (document.all && ! window.opera) {// @todo do we need a more sofisticated MSIE detection?
		this.iframe = document.createElement("iframe");
		this.iframe = this.element.parentNode.insertBefore(this.iframe, this.element);
		this.iframe.style.position = "absolute";
	}
	if (show) {
		var xy = jdom.xy(this.element);
		this.show(xy[0], xy[1]);
	} else {
		this.hide();
	}
}
jdom.layer.zIndex = 1000;
jdom.layer.prototype = {
	show: function(left, top) {
		top = top || 0;
		left = left || 0;
		this.element.style.top = top + 'px';
		this.element.style.left = left + 'px';
		this.zIndex = jdom.layer.zIndex;
		this.element.style.zIndex = this.zIndex;
		jdom.layer.zIndex += 1000;
		jdom.show(this.element);
		if (this.iframe) {
			this.iframe.style.width = this.element.clientWidth + 'px';
			this.iframe.style.height = this.element.clientHeight + 'px';
			this.iframe.style.top = this.element.style.top;
			this.iframe.style.left = this.element.style.left;
			jdom.show(this.iframe);
		}
	},
	hide: function() {
		jdom.hide(this.element);
		jdom.layer.zIndex = this.zIndex;
		this.zIndex = null;
		if (this.iframe) jdom.hide(this.iframe);
	},
	toggle: function(left, top) {
		if (jdom.toggle(this.element)) {
			this.show(left, top);
			return true;
		} else {
			this.hide();
			return false;
		}
	}
}

/**
 * Element position
 */
jdom.xy = function(el) {
	el = jdom(el);
	var x = 0, y = 0;
	if (el.offsetParent) {
		x = el.offsetLeft;
		y = el.offsetTop;
		while (el = el.offsetParent) {
			x += el.offsetLeft;
			y += el.offsetTop;
		}
	}
	return [x, y];
};
jdom.below = function(el) {
	el = jdom(el);
	var xy = jdom.xy(el);
	xy[1] += el.offsetHeight;
	return xy;
};

/**
 * Add CSS class to element
 */
jdom.addClass = function(el, name) {
	el = jdom(el);
	var c = el.className.split(/\s+/);
	c.push(name);
	el.className = c.join(" ");
};
/**
 * Remove CSS class from element
 */
jdom.removeClass = function(el, name) {
	el = jdom(el);
	var c = el.className.split(/\s+/);
	var r = [];
	for (var i = 0; i < c.length; ++i)
	{
		if (name != c[i])
		{
			r.push(c[i]);
		}
	}
	el.className = r.join(" ");
};

/**
 * Working with element attributes ()
 */
/**
 * Get attribute [optionally] in some namespace (i.e. <div x:attr="val">)
 * @param object | string el
 * @param string attribute
 * @param string ns the namespace
 */
 jdom.getAttribute = function(el, attribute, ns) {
    // IE6 fails getAttribute when used on table element
    if (ns) {
    	try {
    		var o = el.getAttributeNS(ns, attribute);
    		if (o) {
    			return o;
    		}
    	} catch (e) {}
    }
    try {
        return el.getAttribute((ns ? (ns + ':') : '') + attribute);
    } catch (e) {}
    return null;
}
/**
 * Set attribute [optionally] in some namespace (i.e. <div x:attr="val">)
 * @param object | string el
 * @param string attribute
 * @param string value
 * @param string ns the namespace
 */
jdom.setAttribute = function(el, attribute, value, ns) {
	if (ns) {
    	try {
			el.setAttributeNS(ns, attribute, value);
    	} catch (e) {}
    }
    
    try {
    	elm.setAttribute((ns ? (ns + ':') : '') + attribute, value);
    } catch (e) {}
}
/**
 * Remove attribute [optionally] in some namespace (i.e. <div x:attr="val">)
 * @param object | string el
 * @param string attribute
 * @param string ns the namespace
 */
jdom.removeAttribute = function(el, attribute, ns) {
	if (ns) {
    	try {
			el.removeAttributeNS(ns, attribute);
    	} catch (e) {}
    }
    try {
		el.removeAttribute(ns, attribute);
	} catch (e) {}
}

/**
 * XMLHttpRequest
 */
jdom.request = function(url) {
	this.url = url;
	this.method = 'GET';
	this.data = null;
	this.onSuccess = function(){};
	this.onFailure = function(){};
	
	if (window.XMLHttpRequest) {
        try {
            this.req = new XMLHttpRequest();
        } catch (e){}
    } else if (window.ActiveXObject) {
        try {
            this.req = new ActiveXObject('Msxml2.XMLHTTP');
        } catch (e){}
        try {
            this.req = new ActiveXObject('Microsoft.XMLHTTP');
        } catch (e){}
    }
}
jdom.request.prototype = {
	/**
	 * perform request
	 */
	go: function() {
		var url = this.url;
		var data = this.data;
		if (this.method == 'GET')
		{
			this.method = 'GET';
			if (url.indexOf('?') == -1)
			{
				url += '?' + data;
			}
			else
			{
				url += '&' + data;
			}
			data = null;
		}
		
		// the Request
		that = this;
		this.req.onreadystatechange = function() {
			if (that.req.readyState == 4) {
				var json;
				try {
					eval ('json = (' + that.req.responseText + ')');
				} catch (e) {
					json = null;
				}
				if (that.req.status == 200) {// OK
					that.onSuccess.call(window, {'json':json, 'request':that.req});
				} else {// Failed for some reason
					that.onFailure.call(window, {'json':json, 'request':that.req});
				}
			}
		};
		this.req.open(this.method, encodeURI(url), true);
		this.req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
		if (this.method == 'POST') {
			var charset = jdom.first(document, 'characterSet', 'charset') || 'utf-8';
			this.req.setRequestHeader("Content-Type", "applcation/x-www-form-urlencoded; charset="+charset);
		}
		this.req.send(encodeURI(data));
	},
	setRequestHeader: function(name, content) {
		this.req.setRequestHeader(name, content);
		return this;
	},
	setSuccessHandler: function(fn, scope) {
		this.onSuccess = function(arg) {
			fn.call(scope || window, arg);
		};
		return this;
	},
	setFailureHandler: function(fn, scope) {
		this.onFailure = function(arg) {
			fn.call(scope || window, arg);
		};
		return this;
	}
}

jdom.updater = function(el, url, force) {
	this.element = jdom(el);
	this.url = url;
	this.force = force;
	this.onLoad = function(){};
}
jdom.updater.prototype = {
	go: function() {
		if (! ('loaded' in this.element) || this.force) {
			this.element.innerHTML = 'Loading...';
			new jdom.request(this.url).
				setSuccessHandler(function(r){
					this.element.innerHTML = r.request.responseText;
					this.element.loaded = true;
					this.onLoad.call(this, r);
				}, this).
				go();
		}
	}
}

/**
 * Get a property from the first object in argument list that appeara to have it
 * @param string name Property name
 * @param object arg1,.. argN
 * @return mixed
 */
jdom.propget = function(name, arg1) {
	for (var i = 1; i < arguments.length; ++i) {
		try {
			if (name in arguments[i]) {
				return arguments[i][name];
			}
		} catch (e) {}
	}
};
jdom.first = function(o, arg1) {
	for (var i = 1; i < arguments.length; ++i) {
		try {
			if (arguments[i] in o) {
				return o[arguments[i]];
			}
		} catch (e) {}
	}
};
jdom.inspect = function(o) {
	var list = [];
	for (var i in o) list.push(i);
	alert(list.join(", "));
};

/**
 * Some tasteful additions to ecmascript...
 */
String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/, "");
}
jdom.merge = function(target, source) {
	for (var i in source) {
		target[i] = source[i];
	}
	return target;
}