Utils = {
	ELEMENT_NODE:1,
	TEXT_NODE:3,
	CDATA_SECTION_NODE:4,

	ParserError: 1,
	LoadError: 2,
	ServerError: 3,
	RequestError: 4,
	
	LoadStatusInit: 0,
	LoadStatusSuccess: 1,
	LoadStatusError: 2,
	
	dir: ".", // realtive directory
	
	Boolean: function(value){
		if(typeof(value) == "string")
			return value == "true";
		else{
			value = Utils.nodeValue(value);
			return value == "true";
		}
	},
	String: function(node){
		if(typeof(node) == "string")
			return String(node);
		else
			return String(Utils.nodeValue(node));
	},
	Number: function(node){
		if(typeof(node) == "string")
			return Number(node);
		else
			return Number(Utils.nodeValue(node));
	},
	
	setValueForKey: function(value, key){
		var setter = Utils._setKey(key);
		if(typeof(this[setter]) == 'function')
			this[setter](value);
		else
			this[key] = value;
	},
	valueForKey: function(key){
		var getter = Utils._getKey(key);
		if(typeof(this[getter]) == 'function')
			return this[getter]();
		else
			return this[key];
	},
	valueForKeyPath: function(path)
	{
		var keys = path.split('.');
		var obj = this;
		for(var n=0;n<keys.length;n++){
			if(obj)
				obj = Utils.valueForKey.apply(obj, [keys[n]]);
			else break;
		}
		return obj;
	},
	setValueForKeyPath: function(value, path)
	{
		var keys = path.split('.');
		var len = keys.length;
		var obj = this;
		for(var n=0;n<len-1;n++){
			if(obj) 
				obj = Utils.valueForKey.apply(obj, [keys[n]]);
			else break;
		}
		if(obj){
			Utils.setValueForKey.apply(obj, [value, keys[len-1]]);
		}
	},
	_setKey: function(key){
		var key0 = key.substr(0,1);
		var key1 = key.substr(1, key.length-1);
		return 'set' + key0.toUpperCase() + key1;
	},
	_getKey: function(key){
		var key0 = key.substr(0,1);
		var key1 = key.substr(1, key.length-1);
		return 'get' + key0.toUpperCase() + key1;
	},
	nodeValue: function(node){
		if(node.childNodes){
			var value = "";
			for(var n=0;n<node.childNodes.length;n++){
				if(node.childNodes[n].nodeValue)
					value += node.childNodes[n].nodeValue;
			}
			return value;
		}
		return null;
	},
	
	_setNodeValue: function(object, node, entity, name, isArray)
	{
		var value = null;
		if(entity[name] && entity[name].objectClass){
			var objEntity = null;
			if(entity[name].entity){
				// entity can be specified as string(value path) or an object itself
				if(typeof entity[name].entity == "string")
					objEntity = Utils.valueForKeyPath.apply(window, [entity[name].entity]);
				else
					objEntity = entity[name].entity;
				if(!objEntity){
					var error = new Error("Invalid entity path '" + entity[name].entity + "'");
					error.entity = entity;
					error.entityName = name;
					throw error;
				}
			}
			value = new entity[name].objectClass(node, objEntity);
		}else
			value = Utils.String(node);
		var key = name;
		if(entity[name] && entity[name].name)
			key = entity[name].name;
		if(isArray){
			var array = Utils.valueForKey.apply(object, [key]);
			if(!array || !(array instanceof Array))
				Utils.setValueForKey.apply(object, [array = new Array(), key]);
			array.push(value);
		}else
			Utils.setValueForKey.apply(object, [value, key]);
	},
	
	_setObjectProperties: function(object, el, entity){
		if(el && el.childNodes){
			for(var n=0;n<el.childNodes.length;n++){
				var node = el.childNodes[n];
				if(node.nodeType == 1){
					var isArray = false;
					var name = node.nodeName;
					if(entity[name] && entity[name].array)
						isArray = true;
					Utils._setNodeValue(object, node, entity, name, isArray);
				}
			}
		}
		if(el && entity.attributes){
			for(var name in entity.attributes){
				var value = el.getAttribute(name);
				if(value != undefined){
					if(typeof(entity.attributes[name]) == "function")
						object[name] = entity.attributes[name](value);
					else if(entity.attributes[name].name && entity.attributes[name].objectClass)
						object[entity.attributes[name].name] = entity.attributes[name].objectClass(value);
						
//					if(entity.attributes[name] instanceof Array){
//						if(entity.attributes[name].length >= 2)
//							object[entity.attributes[name][1]] = entity.attributes[name][0](value);
					else{ // error
						throw "Invalid entity attributes";
					}
//					}else
//						object[name] = entity.attributes[name](value);
				}
			}
		}
	},
	
	XMLObject: function(el, entity){
		if(!entity)
			throw new Error("Utils.XMLObject: no entity for element " + el.nodeName);
		Utils._setObjectProperties(this, el, entity);
		return this;
	},
	
	getXMLHttpRequest: function()
	{
		var rq = null;
		if(window.XMLHttpRequest) 
			try{
				rq = new XMLHttpRequest();
			}catch(e){
				rq = null;
			}
		else if(window.ActiveXObject)
			try{
				rq = new ActiveXObject("Msxml2.XMLHTTP");
			}catch(e){
				try{
					rq = new ActiveXObject("Microsoft.XMLHTTP");
				}catch(e){
					rq = null;
				}
			}
		return rq;
	},

	_encodeParams: function(obj, noescape)
	{
		var array = new Array();
		if(obj) for(var name in obj)
			if(obj[name])
				array.push(name + '=' + (noescape? obj[name] : escape(obj[name])));
		return array.join('&');
	},

	_parseParams: function(str, params)
	{
		if(!params)
			params = new Object();
		if(str){
			var array = str.split('&');
			for(var n=0;n<array.length;n++){
				var pair = array[n].split('=');
				params[pair[0]] = unescape(pair[1]);
			}
		}
		return params;
	},

	dateToString: function(aDate)
	{
		if(aDate){
			var date = new Date();
			var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
			var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
			var wd = aDate.getDay();
			var dd = aDate.getDate();
			var mm = aDate.getMonth();
			var yy = aDate.getFullYear();
			var str = months[mm] + ' ' + dd;
			if(date.getFullYear() != yy)
				str += ', ' + yy;
			return str;
		}else
			return '';
	},

	sendHTTPRequest: function(url, request, handler, onLoad, onError, model)
	{
		var rq = Utils.getXMLHttpRequest();
		if(rq) try{
			rq.onreadystatechange = function() {
				if(rq.readyState == 4){
					if(rq.status == 200){ // HTTP OK
						var el = rq.responseXML.documentElement;
						if(el.nodeName == 'parsererror'){
							if(onError)
								onError.apply(handler, [Utils.ParserError, el]);
						}else if(onLoad)
							try{
								if(model)
									el = new Utils.XMLObject(el, model);
								onLoad.apply(handler, [el]);
							}catch(e){
								if(onError)
									onError.apply(handler, [Utils.LoadError, e]);
							}
						
					}else if(onError)
						onError.apply(handler, [Utils.ServerError, rq.status]);
				}
			}
			var str = Utils._encodeParams(request);
			if(str)
				rq.open("GET", url + "?" + str, true);
			else
				rq.open("GET", url, true);
			rq.send(null);
		}catch(e){
			if(onError)
				onError.apply(handler, [Utils.RequestError, e]);
		}
		else if(onError)
			onError.apply(handler, [Utils.RequestError, new Error("cannot create request")]);
	},

	loadHTMLText: function(path, handler, onLoad, onError)
	{
		var rq = Utils.getXMLHttpRequest();
		if(rq) 
			try{
				rq.onreadystatechange = function() {
					if(rq.readyState == 4){
						var obj = null;
						if(rq.status == 200){ // HTTP OK
							if(onLoad){
								obj = {text:rq.responseText, success:0, name:path};
								onLoad.apply(handler, [obj]);
							}
						}else if(onError){
							obj = {text:"<h4>Load request error:</h4><p>server returned status: " + rq.status + "</p>",
								success:1, name:path, status:rq.status};
							onError.apply(handler, [obj]);
						}
					}
				}
				var location = Utils.dir + "/" + path;
				rq.open('GET', location, true);
				rq.send(null);
			}catch(e){
				var obj = {text:"<h4>Load request error:</h4><p>" + e + "</p>", success:2, name:path, error:e};
				if(onError)
					onError.apply(handler,[obj]);
			}
		else{
			var obj = {text:"<h4>Load request error:</h4><p>cannot create request</p>", success:3, name:path};
			if(onError)
				onError.apply(handler, obj);
		}
	}
};
