/*
** Global utility functions.
** Updated: 2006.09.14
** Author: Sergey Busel
*/

var _GLOBAL_JS_ = true;
var _GLOBAL_JS_VER_ = 20060914;

document.element = document.getElementById;
document.elements = document.getElementsByName;
document.tagelements = document.getElementsByTagName;

String.prototype.trim = function() {
	return this.replace(/^\s*/, "").replace(/\s*$/, "");
};

String.prototype.trimNL = function() {
	return this.replace(/^\s*/, "").replace(/\s*$/, "").replace(/\n/g, "");
};

String.prototype.endsWith = function(str, flags) {
	var re = new RegExp(str + "$", flags);
	return re.test(this);
};

String.prototype.startsWith = function(str, flags) {
	var re = new RegExp("^" + str, flags);
	return re.test(this);
};

String.prototype.htmlEncode = function()
{
	return this.replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/\"/g, "&quot;").replace(/&/g, "&amp;");
};

String.prototype.htmlDecode = function()
{
	return this.replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&quot;/g, "\"").replace(/&amp;/g, "&");
};

Array.prototype.indexOf = function(x)
{
	for(var i=0; i<this.length; i++)
	{
		if(this[i] == x)
			return i;
	}
	return -1;
};

// asynchronous request helper
Async = {
	// returns an asyncronous request object
	request: function() {
		try { return new AcitveXObject("MSXML2.XMLHTTP"); }
		catch(e) {
			try { return new ActiveXObject("Microsoft.XMLHTTP"); }
			catch(e) {
				try { return new XMLHttpRequest(); }
				catch(e) { throw new Error("Could not create request object. It is recommended that you use Internet Explorer browser."); }
			}
		}
	},
	// sends a get request to url and calls the callback function with responseText as argument
	doget: function(url, callback, args) {
		var req = this.request();
		req.onreadystatechange = function() {
			if(req.readyState == 4) {
				if(callback != null && typeof(callback) == "function") {
					var res = req.responseText;
					req = null;
					try { callback(res, args); }
					catch(e) {}
				}
			}
		};
		req.open("get", url, true);
		req.setRequestHeader("Cache-Control", "no-cache no-store");
		req.send(null);
	},
	// sends a post request to url using data and calls the callback function with responseText as argument
	dopost: function(url, data, callback, args) {
		var req = this.request();
		req.onreadystatechange = function() {
			if(req.readyState == 4) {
				if(callback != null && typeof(callback) == "function") {
					var res = req.responseText;
					req = null;
					try { callback(res, args); }
					catch(e) {}
				}
			}
		};
		req.open("post", url, true);
		req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		req.send(data);
	},
	// sends a get request to url and calls the callback function with JSON object retured by server and error (if any)
	jsonget: function(url, callback, args) {
		var req = this.request();
		req.onreadystatechange = function() {
			if(req.readyState == 4) {
				if(callback != null && typeof(callback) == "function") {
					var res = null, err = null;
					try { res = Json.parse(req.responseText); }
					catch(e) { res = null; err = e; }
					req = null;
					try { callback(res, err, args); }
					catch(e) {}
				}
			}
		};
		req.open("get", url, true);
		req.setRequestHeader("Cache-Control", "no-cache no-store");
		req.send(null);
	},
	// sends a post request to url and calls the callback function with JSON object retured by server and error (if any)
	jsonpost: function(url, data, callback, args) {
		var req = this.request();
		req.onreadystatechange = function() {
			if(req.readyState == 4) {
				if(callback != null && typeof(callback) == "function") {
					var res = null, err = null;
					try { res = Json.parse(req.responseText); }
					catch(e) { res = null; err = e; }
					req = null;
					try { callback(res, err, args); }
					catch(e) {}
				}
			}
		};
		req.open("post", url, true);
		req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		req.send(data);
	}
};

// Json object
Json = {
	// parses a JSON string into object
	parse: function(s)
	{
		try
		{
			return eval("(" + s + ")");
		}
		catch(er)
		{
			throw new Error("Could not parse the object from string.\n" + er.description);
		}
	},
	// serializes given object into JSON string
	serialize: function(obj, doFunc)
	{
		try
		{
			var x = new Serializable(obj, doFunc);
			return x.toString();
		}
		catch(er)
		{
			throw new Error("Unable to serialize the given object.\n" + er.description);
		}
	}
};

// DOM helper
Dom = {
	// gets an element from current document by the specified id
	el: function(name) {
		return this.get(name);
	},
	getlist: function(name) {
		var list = document.getElementsByName(name);
		if(list == null)
			list = [];
		return list;
	},
	get: function(name) {
		var obj = document.getElementById(name);
		if(obj == null) {
			obj = document.getElementsByName(name);
			if(obj && obj.length > 0)
				obj = obj[0];
			else
				obj = null;
		}
		return obj;
	},
	getbyval: function(name, val) {
		var list = this.getlist(name);
		for(var i=0; i<list.length; i++) {
			var c = list[i];
			var v = c.value.toLowerCase();
			if(v == val.toLowerCase())
				return c;
		}
		return null;
	},
	getbyattr: function(attrName, attrValue) {
		var elems = document.tagelements("*");
		var list = [];
		var elem, attr, attrval;
		for(var i=0; i<elems.length; i++) {
			elem = elems[i];
			attr = elem.getAttribute(attrName);
			if(attr != null) {
				if(attrValue == null)
					list.push(elem);
				else if(attr == attrValue)
					list.push(elem);
			}
		}
		return list;
	},
	// gets the next sibling element of the specified node
	nextElement: function(obj) {
		if(obj == null) return null;
		while(obj.nextSibling != null) {
			obj = obj.nextSibling;
			if(obj.nodeType == 1)
				return obj;
		}
		return null;
	},
	// gets the previous sibling element of the specified node
	prevElement: function(obj) {
		if(obj == null) return null;
		while(obj.previousSibling != null) {
			obj = obj.previousSibling;
			if(obj.nodeType == 1)
				return obj;
		}
		return null;
	},
	// gets the first child element of the specified node
	childElement: function(obj) {
		if(obj == null) return null;
		for(var i=0; i<obj.childNodes.length; i++) {
			if(obj.childNodes[i].nodeType == 1)
				return obj.childNodes[i];
		}
		return null;
	}
};
var DOM = Dom;
var $ = Dom.get;

// Element node wrapper. Only deals with element nodes ignoring text and empty space nodes.
function DomElement(target)
{
	var obj = target;
	if(!obj) throw new Error("Could not create DomElement from NULL object.");
	if(obj.nodeType != 1) throw new Error("Could not create DomElement from a non-element object.");
	// gets the first child element
	this.firstChild = function()
	{
		if(!obj) return null;
		for(var i=0; i<obj.childNodes.length; i++)
		{
			if(obj.childNodes[i].nodeType == 1)
				return new DomElement(obj.childNodes[i]);
		}
		return null;
	};
	// gets the previous sibling element
	this.prev = function()
	{
		if(obj == null) return null;
		var e = obj;
		while(e.previousSibling != null)
		{
			e = e.previousSibling;
			if(e.nodeType == 1)
				return new DomElement(e);
		}
		return null;
	};
	// gets the next sibling element
	this.next = function()
	{
		if(obj == null) return null;
		var e = obj;
		while(e.nextSibling != null)
		{
			e = e.nextSibling;
			if(e.nodeType == 1)
				return new DomElement(e);
		}
		return null;
	};
	// gets the parent element
	// if tagname argument is given looks for parent element with specified tagName
	this.parent = function(tagname)
	{
		if(!obj) return null;
		var e = obj.parentNode;
		if(tagname)
		{
			tagname = tagname.toUpperCase();
			while(e)
			{
				if(e.nodeType == 1 && e.tagName && e.tagName.toUpperCase() == tagname)
					break;
				e = e.parentNode;
			}
		}
		else
		{
			while(e && e.nodeType != 1)
			{
				e = e.parentNode;
			}
		}
		if(e)
			return new DomElement(e);
		else
			return null;
	};
	// gets the actual element in the document
	this.element = function()
	{
		return obj;
	};
}

// Url utilities
Url = {
	// private variables
	_qs: null,
	// get the host name of the current url
	getHost: function() {
		return window.location.hostname;
	},
	// gets the root of the current url
	getRoot: function() {
		var path = window.location.pathname;
		return path.slice(0, path.indexOf("/", 1));
	},
	getPath: function() {
		var path = window.location.pathname;
		return path.substr(path.indexOf("/", 1));
	},
	// redirects to the given page
	redirect: function(url) {
		if(url == null || url == "")
			return;
		if(url.charAt(0) == "/") {
			window.location = this.getRoot() + escape(url);
		}
		else {
			var path = window.location.pathname;
			path = path.slice(0, path.lastIndexOf("/") + 1);
			path += url;
			window.location = escape(path);
		}
	},
	// redirects to the given page (without escaping url)
	redirect2: function(url) {
		if(url == null || url == "")
			return;
		if(url.charAt(0) == "/") {
			window.location = this.getRoot() + url;
		}
		else {
			var path = window.location.pathname;
			path = path.slice(0, path.lastIndexOf("/") + 1);
			path += url;
			window.location = path;
		}
	},
	// returns a new url string
	// returned string cannot be used with the redirect method
	rewrite: function(url) {
		if(url == null || url == "")
			return "";
		if(url.charAt(0) == "/") {
			return this.getRoot() + url;
		}
		else {
			var path = window.location.pathname;
			path = path.slice(0, path.lastIndexOf("/") + 1);
			path += url;
			return path;
		}
	},
	// returns a new path string excluding the application root
	// returned string can be used with the redirect method
	rewritePath: function(url) {
		if(url == null || url == "")
			return "";
		var path = this.getPath();
		path = path.slice(0, path.lastIndexOf("/") + 1);
		path += url;
		return path;
	},
	// gets an array of query string params
	getParams: function() {
		if(this._qs != null)
			return this._qs;
		var qs = window.location.search.substr(1);
		var pairs = qs.split("&");
		var params = [];
		for(var i=0; i<pairs.length; i++) {
			var pair = pairs[i].split("=");
			params[unescape(pair[0])] = unescape(pair[1]);
		}
		this._qs = params;
		return params;
	},
	// gets the value of a given query string parameter
	getParam: function(key) {
		var qs = this.getParams();
		return qs[key];
	}
};

// EventLocation class.
function EventLocation(e)
{
	this.x = 0;
	this.y = 0;
	if(e.clientX || e.clientY)
	{
		this.x = e.clientX + document.body.scrollLeft;
		this.y = e.clientY + document.body.scrollTop;
	}
	else if(e.pageX || e.pageY)
	{
		this.x = e.pageX;
		this.y = e.pageY;
	}
}

// StrBuf class. Should be faster then string concatination.
function StrBuf(iniStr)
{
	var elems = [];
	var len = 0;
	
	this.append = function(str)
	{
		if(str)
		{
			elems.push(str);
			len = len + str.length;
		}
	};
	this.prepend = function(str)
	{
		if(str)
		{
			elems.unshift(str);
			len = len + str.length;
		}
	};
	this.clear = function()
	{
		elems.length = 0;
		len = 0;
	};
	this.length = function()
	{
		return len;
	};
	this.toString = function()
	{
		return elems.join("");
	};
	this.to_str = function(separator)
	{
		return elems.join(separator);
	};
	
	if(iniStr)
		this.append(iniStr);
}

// Helps build async request URL.
function UrlBuilder(iniUrl)
{
	var url = "";
	var params = [];
	
	var Item = function(key, value)
	{
		this.key = key;
		this.value = value;
	};
	
	this.setUrl = function(u)
	{
		url = u;
	};
	
	this.add = function(key, value)
	{
		if(!key)
			throw new Error("Could not add parameter with NULL key.");
		params.push(new Item(key, value));
	};
	
	this.toString = function()
	{
		var s = new StrBuf();
		var flag = false;
		if(url && url != "")
		{
			s.append(url);
			s.append("?");
		}
		for(var i=0; i<params.length; i++)
		{
			if(flag)
				s.append("&");
			var item = params[i];
			s.append(item.key);
			s.append("=");
			s.append(escape(item.value));
			flag = true;
		}
		return s.toString();
	}
	
	if(iniUrl && iniUrl != "")
		this.setUrl(iniUrl);
}

// Allows to serialize objects into JSON strings.
// Serializes all public members of given objects recursively.
function Serializable(obj, doFunc)
{
	if(obj == null)
		throw new Error("Could not create a Serializable for NULL object.");
	var o = obj;
	var func = doFunc || false;
	var handler;
	
	var _string = function()
	{
		o = o.replace(/'/g, "").replace(/"/g, '\\\"');
		return "\"" + o + "\"";
	};
	
	var _number = function()
	{
		return o.toString();
	};
	
	var _array = function()
	{
		var buf = new StrBuf("[");
		var len = o.length;
		var flag = len - 1;
		for(var i=0; i<len; i++)
		{
			var item = new Serializable(o[i]);
			var v = item.toString();
			if(v != null && v != "")
			{
				buf.append(v);
				if(i < flag)
					buf.append(",");
			}
		}
		buf.append("]");
		return buf.toString();
	};
	
	var _object = function()
	{
		var buf = new StrBuf("{");
		var flag = false;
		for(var k in o)
		{
			var item = new Serializable(o[k]);
			var v = item.toString();
			if(v != null && v != "")
			{
				if(flag)
					buf.append(",");
				else
					flag = true;
				buf.append(k);
				buf.append(":");
				buf.append(v);
			}
		}
		buf.append("}");
		return buf.toString();
	};
	
	var _function = function()
	{
		try
		{
			return o.toString();
		}
		catch(e)
		{
			return "null";
		}
	};
	
	var _bool = function()
	{
		return (o) ? "true" : "false";
	};
	
	var _default = function()
	{
		return "";
	};
	
	this.toString = function()
	{
		return handler();
	};
	
	if(o.constructor === String)
		handler = _string;
	else if(o.constructor === Number)
		handler = _number;
	else if(o.constructor === Array)
		handler = _array;
	else if(o.constructor === Function)
	{
		if(func)
			handler = _function;
		else
			handler = _default;
	}
	else if(typeof(o) === "boolean")
		handler = _bool;
	else if(o.constructor === Object || typeof(o) === "object")
		handler = _object;
	else
		throw new Error("Unsupported type passed to Serializable constructor.");
}

