/* Utils object for odd jobs
@requires XO.js
@requires Error.js
*/

var XUtil = new XO(
{
	/*
	some native functions return what appears to be an array, but typeof is() it
	an "object". It has a numeric keys and a length member, but for whatever reason
	it doesn't descend from the Array prototype. XUtil.toArray(obj) will give it
	the Array.prototype extentions we wanted for them.
	@param object (array-like but somehow not an instanceof Array)
	@return Array
	*/
	toArray:function(obj)
	{
		var ret=[];
		for(var a = 0; a<obj.length; a++)
			ret.push(obj[a]);
		return ret;
	},
	
	toNum:function(str)
	{
		if( !['string','number','boolean'].has( is(str,'type') ) )
		{
			Error.IllegalArgument(new Error() , 'scalar value' , str)
			return 0;
		}
		
		var i = (+str);
		if(!isNaN(i))
				return i;
	   
		i = parseFloat(str.replace(/\b([^0-9\.]+)/g,''));
		if(!isNaN(i))
				return i;
	   
		return 0;
	},
	
	
	/*
	For ajax request returns, or where HTML/XML is passed as a string,
	this function creates a dummy div that will hold the contents, minus any <script> tags.
	The innerHTML is now a node tree you can manipulate.
	@param string SGML
	@return object DIV
	*/
	stringToNode:function(string)
	{
		var div = document.createElement('DIV');
		div.style.display='inline';
		if(!is(string.replace,'function'))
			Error.IllegalArgument( new Error("XUtil.stringToNode") , 'String' , string);
		else
			div.innerHTML = string.replace(/<script(.|\s)*?\/script>/g, "");
		return div;
	},
	
	
	/*
	A slightly shorter way to do obj.getElementsByTagName(tag), but with the added
	benefit of having the return object be a real array and not a enumerable object,
	allowing use the native and extended Array methods.
	*/
	getTags:function(obj,tag)
	{
		if(!is(tag) && is(obj,'string')) // perhaps we said ('li') meaning (document, 'li')
		{
			tag = obj;
			obj = document;
		}
		
		if(!is(obj,'object'))
			return Error.IllegalArgument( new Error("XUtil.getTags") , 'HTML Object' , obj);
		if(!is(tag,'string'))
			return Error.IllegalArgument( new Error("XUtil.getTags") , 'String - tagName' , tag);
		
		return XUtil.toArray( (function(){ return this.getElementsByTagName(tag)}).call(obj) );
	},
	
	/**
	Still use obj.getAttribute(a) to get a value for a single attribute.
	Use getAttribs(obj) to attempt to get them all (including any DOM property that is scalar).
	@param object
	@return object property:value pairs
	*/
	getAttribs:function(obj)
	{
		if(!is(obj,'object'))
			return Error.IllegalArgument( new Error("XUtil.getAttribs") , 'HTML Object' , obj);
		
		var attribs={};
		for(var a in obj)
		{
			if(is(obj[a]) && (is(obj[a],'string') || is(obj[a],'number')))
			{
				eval('attr={'+a+':obj[a]}');
				XO.x(attribs,attr);
			}
		}
		return attribs;
	},
	
	
	/*
	Returns, as best it can, the true four corner position of an object
	@param object
	@return object  {top , bottom, left, right}
	*/
	getPosition:function(obj)
	{
		if(!is(obj,'object'))
			return Error.IllegalArgument( new Error("XUtil.getPosition") , 'HTML Object' , obj);
		
		var left = 0, top = 0, ret = {};
		o=new XO(obj);
		while (is(o.offsetParent))
		{
			left += o.offsetLeft;
			top += o.offsetTop;
			o = o.offsetParent;
		}
		
		left += o.offsetLeft;
		top += o.offsetTop;
		
		ret.left=left;
		ret.top=top;
		
		var css = XUI.getCSS(obj);
		ret.right = ret.left + this.toNum(css['width']);
		ret.bottom = ret.top + this.toNum(css['height']);
		
		return ret;
	},
	
	keys:function(obj)
	{
		if(is(obj,'array'))
			return Math.range(0,obj.length-1);
		
		var r=[];
		if(is(obj,'object'))
		{
			for(var i in obj)
				r.push(i);
		}
		return r;
	},
	
	sprintf:function()
	{
		/** (modified)
		*  Javascript sprintf
		*  http://www.webtoolkit.info/
		**/

		if (typeof arguments == "undefined") { return null; }
		if (arguments.length < 1) { return null; }
		if (typeof arguments[0] != "string") { return null; }
		if (typeof RegExp == "undefined") { return null; }

		var string = arguments[0];
		var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
		var matches = new Array();
		var strings = new Array();
		var convCount = 0;
		var stringPosStart = 0;
		var stringPosEnd = 0;
		var matchPosEnd = 0;
		var newString = '';
		var match = null;
		
		var convert = function(match, nosign){
				if (nosign) {
					match.sign = '';
				} else {
					match.sign = match.negative ? '-' : match.sign;
				}
				var l = match.min - match.argument.length + 1 - match.sign.length;
				var pad = new Array(l < 0 ? 0 : l).join(match.pad);
				if (!match.left) {
					if (match.pad == '0' || nosign) {
						return match.sign + pad + match.argument;
					} else {
						return pad + match.sign + match.argument;
					}
				} else {
					if (match.pad == '0' || nosign) {
						return match.sign + match.argument + pad.replace(/0/g, ' ');
					} else {
						return match.sign + match.argument + pad;
					}
				}
			}

		while (match = exp.exec(string)) {
			if (match[9]) { convCount += 1; }

			stringPosStart = matchPosEnd;
			stringPosEnd = exp.lastIndex - match[0].length;
			strings[strings.length] = string.substring(stringPosStart, stringPosEnd);

			matchPosEnd = exp.lastIndex;
			matches[matches.length] = {
				match: match[0],
				left: match[3] ? true : false,
				sign: match[4] || '',
				pad: match[5] || ' ',
				min: match[6] || 0,
				precision: match[8],
				code: match[9] || '%',
				negative: parseInt(arguments[convCount]) < 0 ? true : false,
				argument: String(arguments[convCount])
			};
		}
		strings[strings.length] = string.substring(matchPosEnd);

		if (matches.length == 0) { return string; }
		if ((arguments.length - 1) < convCount) { return null; }

		var code = null;
		var match = null;
		var i = null;

		for (i=0; i<matches.length; i++) {

			if (matches[i].code == '%') { substitution = '%' }
			else if (matches[i].code == 'b') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(2));
				substitution = convert(matches[i], true);
			}
			else if (matches[i].code == 'c') {
				matches[i].argument = String(String.fromCharCode(parseInt(Math.abs(parseInt(matches[i].argument)))));
				substitution = convert(matches[i], true);
			}
			else if (matches[i].code == 'd') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)));
				substitution = convert(matches[i]);
			}
			else if (matches[i].code == 'f') {
				matches[i].argument = String(Math.abs(parseFloat(matches[i].argument)).toFixed(matches[i].precision ? matches[i].precision : 6));
				substitution = convert(matches[i]);
			}
			else if (matches[i].code == 'o') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(8));
				substitution = convert(matches[i]);
			}
			else if (matches[i].code == 's') {
				matches[i].argument = matches[i].argument.substring(0, matches[i].precision ? matches[i].precision : matches[i].argument.length)
				substitution = convert(matches[i], true);
			}
			else if (matches[i].code == 'x') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
				substitution = convert(matches[i]);
			}
			else if (matches[i].code == 'X') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
				substitution = convert(matches[i]).toUpperCase();
			}
			else {
				substitution = matches[i].match;
			}

			newString += strings[i];
			newString += substitution;

		}
		newString += strings[i];

		return newString;
			
	}
	//*/
});
