//
//	JavaScript utils v0.1
//	http://stfw.hu
//	03/14/08
//
//	Copyright (C) 2008, Attila Magyar
//	
//	All rights reserved.
//
//	Redistribution and use in source and binary forms, with or without
//	modification, are permitted provided that the following conditions are met:
//		* Redistributions of source code must retain the above copyright notice,
//		  this list of conditions and the following disclaimer.
//		* Redistributions in binary form must reproduce the above copyright
//		  notice, this list of conditions and the following disclaimer in th
//		  documentation and/or other materials provided with the distribution.
//		* Neither the name of the <ORGANIZATION> nor the names of its
//		  contributors may be used to endorse or promote products derived from
//		  this software without specific prior written permission.
//
//	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//	A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
//	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//

function $(sId) { return ('string' == typeof(sId)) ? document.getElementById(sId) : sId; }
function $C(sTagName) { return document.createElement(sTagName); }

var Utils = {
	$htmlUnescape : null,
	$debug : [],
	$debugOn : true,
	
	debugConsole :
	{
		clear : function ()
		{
			Utils.$debug = [];
		},
		
		write : function (s)
		{
			if (!Utils.$debugOn) return;
			Utils.$debug[Utils.$debug.length] = (new Date()).getTime() + ': ' + s;
		},
		
		read : function (nLines)
		{
			if (!nLines) nLines = -1;
			var s = '';
			var start = (0 < nLines) ? Math.max(0, Utils.$debug.length - nLines) : 0; 
			for (var i = start, l = Utils.$debug.length; i != l; ++i)
			{
				s = s + Utils.$debug[i] + "\n";
			}
			return s;
		}
	},
	
	toStr : function (object, recLevel, padStr)
	{
		if (typeof(object) != 'object')
			return object;
		var s = '';
		if (!padStr)
			padStr = '';
		for (var i in object)
		{
			s += padStr + i + ': ' + (recLevel ? Utils.toStr(object[i], recLevel - 1, padStr + ' ') : object[i]) + '\n'; 
		}
		return s;
	},

	htmlUnescape : function (sText)
	{
		if (!Utils.$htmlUnescape)
		{
			Utils.$htmlUnescape = $C('textarea');
		}
		Utils.$htmlUnescape.innerHTML = sText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
		return Utils.$htmlUnescape.value;
	},

	Dom :
	{
		createFilterObject : function (filter)
		{
			if (typeof(filter) == 'object')
				return filter;
			var f = new String(filter);
			var fo = {};
			var m = f.match(/\.([^.#\r\n\t ]+)/);
			if (m && m.length)
				fo.className = m[1];
			m = f.match(/^[a-z0-9A-Z]+/);
			if (m && m.length)
				fo.tagName = m[0];
			m = f.match(/#([^.#\r\n\t ]+)/);
			if (m && m.length)
				fo.id = m[1];
			m = f.match(/\[([a-z]+(="[^"]*")?)(,\s*([a-z]+(="[^"]*")?))*?\]/);
			if (m)
			{
				var attrs = [];
				var n = '', v = '';
				var s;
				for (var i = 0, l = m.length; i != l; ++i)
				{
					if (typeof(m[i]) != 'undefined')
					{
						if (s = m[i].match(/^([a-z]+)(="([^"]*)")?$/))
						{
							var o = { name : s[1] };
							if (2 < s.length)
								o.value = s[3];
							attrs.push(o);
						}
					}
				}
				if (attrs.length)
					fo.attributes = attrs;
			}
			return fo;
		},
		
		test : function (object, filter)
		{
			var fo = Utils.Dom.createFilterObject(filter);
			object = $(object);
			if (typeof(object) != 'object')
				return false;
			if (fo.tagName && (!(object.tagName && object.tagName.toLowerCase() == fo.tagName.toLowerCase())))
				return false;
			if (fo.id && (!(object.id && object.id.toLowerCase() == fo.id.toLowerCase())))
				return false;
			if (fo.className)
			{
				var c = new RegExp('(\\s+' + fo.className + '$)|(^' + fo.className + '\\s+)|(\\s+' + fo.className + '\\s+)');
				var oc = new String(object.className ? object.className : object.getAttribute('class'));
				if (oc != fo.className && (!c.test(oc)))
					return false;
			}
			if (fo.attributes)
			{
				var attr;
				for (var i = 0, l = fo.attributes.length; i != l; ++i)
				{
					attr = object[fo.attributes[i].name]
						? object[fo.attributes[i].name]
						: object.getAttribute(object[fo.attributes[i].name]);
					if (fo.attributes[i].value)
					{
						if (!(attr && attr == fo.attributes[i].value))
							return false;
					}
					else
						if (!attr)
							return false;
				}
			}
			return true;
		},
		
		getElementsByClassName : function (sClassName)
		{
			var nodes = document.getElementsByTagName('*');
			var ret = []; 
			var fo = Utils.Dom.createFilterObject('.' + sClassName);
			for (var i = 0, l = nodes.length; i < l; ++i)
			{
				if (Utils.Dom.test(nodes[i], fo))
					ret[ret.length] = nodes[i];
			}
			return ret;
		},
		
		down : function (oElement, filter)
		{
			var fo = Utils.Dom.createFilterObject(filter);
			var o = $(oElement);
			if (!(o && o.childNodes))
				return [];
			var r = [];
			for (var i = 0, l = o.childNodes.length; i != l; ++i)
			{
				if (Utils.Dom.test(o.childNodes[i], fo))
					r.push(o.childNodes[i]);
				r = r.concat(Utils.Dom.down(o.childNodes[i], fo));
			}
			return r;
		},
		
		up : function (oElement)
		{		
			var fo = Utils.Dom.createFilterObject(filter);
			var o = $(oElement);
			if (!o)
				return [];
			o = o.parentNode;
			var r = [];
			while (o)
			{
				if (Utils.Dom.test(o, fo))
				{
					r.push(o);
				}
				else
					o = o.parentNode;
			}
			return r;
		},
		
		setState : function (oObject, oAttributes)
		{
			var ex;
			oObject = $(oObject);
			for (var i in oAttributes)
			{
				if (('object' == typeof(oAttributes[i])) && ('object' == typeof(oObject[i])))
				{
					Utils.Dom.setState(oObject[i], oAttributes[i]);
				}
				else
				{
					try
					{
						oObject[i] = oAttributes[i];
					}
					catch (ex)
					{
						throw oObject + '.' + i + ' could not be set to ' + oAttributes[i] + '. (' + ex + ')';
					}
				}
			}
		}		
	},
	 
	Screen :
	{
		hideElements : function (aTagNames)
		{
			var hidden = [];
			for (var i = 0, l = aTagNames.length; i < l; ++i)
			{
				var elements = document.getElementsByTagName(aTagNames[i]);
				for (var j = 0, k = elements.length; j < k; ++j)
				{
					hidden[hidden.length] = elements[j];
					elements[j].style.visibility = 'hidden'; 
				}
			}
			return hidden;
		},
		
		showElements : function (aHidden)
		{
			for (var i = 0, l = aHidden.length; i < l; ++i)
			{
				aHidden[i].style.visibility = 'visible';
			}
		},
	
		getScrollPosition : function (wWindow)
		{
			if (!wWindow) wWindow = window;
			var sTop = 0;
			var sLeft = 0;
			if (null!=wWindow.pageYOffset)
			{
				sLeft = wWindow.pageXOffset;
				sTop = wWindow.pageYOffset;
			}
			else
			{
				var ex;
				try
				{
					sLeft = wWindow.document.body.scrollLeft;
					sTop = wWindow.document.body.scrollTop;
				}
				catch (ex)
				{
					sLeft = wWindow.document.documentElement.scrollLeft;
					sTop = wWindow.document.documentElement.scrollTop;
				}
			}
			return {
				top : sTop,
				left : sLeft
			};
		},
	
		getWindowSize : function (wWindow)
		{
			var windowWidth = 0;
			var windowHeight = 0;
			if (!wWindow) wWindow = window;
			var ex;
			try
			{
				windowWidth = wWindow.document.body.clientWidth;
				windowHeight = wWindow.document.body.clientHeight;
				if (0 == windowWidth * windowHeight) throw 'Window size problem.';
			}
			catch (ex)
			{
				try {
					windowWidth = wWindow.document.documentElement.clientWidth;
					windowHeight = wWindow.document.documentElement.clientHeight;
					if (0 == windowWidth * windowHeight) throw 'Window size problem.';
				}
				catch (ex)
				{
					try
					{
						windowWidth = wWindow.innerWidth;
						windowHeight = wWindow.innerHeight;
						if (0 == windowWidth * windowHeight) throw 'Window size problem.';
					}
					catch (ex)
					{
						return false;
					}
				}
			}
			return {
				width : windowWidth,
				height : windowHeight
			};
		},
	
		getElementSize : function (oElement)
		{
			oElement = $(oElement);
			var h = 0;
			var w = 0;
			var ex;
			try
			{
				w = oElement.offsetWidth
					? oElement.offsetWidth
					: (
						oElement.clip 
							? oElement.clip.width
							: oElement.style.pixelWidth
					);
				h = oElement.offsetHeight
					? oElement.offsetHeight
					: (
						oElement.clip
							? oElement.clip.height
							: oElement.style.pixelHeight
					);
				return { width : w * 1, height : h * 1 };
			}
			catch (ex)
			{
				return false;
			}
		},
	
		getElementPosition : function (oElement)
		{
			oElement = $(oElement);
			var ex;
			var size = Utils.Screen.getElementSize(oElement);
			if (size)
			{
				try
				{
					var ret = {
						top : oElement.offsetTop,
						left : oElement.offsetLeft,
						width : size.width,
						height : size.height
					};
					while (oElement = oElement.offsetParent)
					{
						ret.top += oElement.offsetTop;
						ret.left += oElement.offsetLeft;
					}
					return ret;
				}
				catch (ex)
				{
					return false;
				}
			}
			else return false;
		},

		updatePosition : function (oElement, pos)
		{
			oElement = $(oElement);
			oElement.style.top = pos.top + 'px';
			oElement.style.left = pos.left + 'px';
			oElement.style.height = pos.height + 'px';
			oElement.style.width = pos.width + 'px';
		}
	},

	Events :
	{
		addListener : function (oObject, sEventName, fHandler)
		{
			oObject = $(oObject);
			var wrapper = function (oEvent)
			{
				if (!oEvent) oEvent = window.event;
				var oSource = oEvent.srcElement ? oEvent.srcElement : this;
				return fHandler(oEvent, oSource);
			}
			if (oObject.addEventListener)
			{
				oObject.addEventListener(
					sEventName,
					wrapper,
					false
				);
				return wrapper;
			}
			else
			{
				if (oObject.attachEvent)
				{
					oObject.attachEvent('on' + sEventName, wrapper);
					return wrapper;
				}
			}
			return false;
		},
		
		removeEventHandler : function (oObject, sEventName, fHandler)
		{
			oObject = $(oObject);
			if (oObject.removeEventListener)
			{
				oObject.removeEventListener(sEventName, fHandler, false);
				return true;
			}
			else
			{
				if (oObject.detachEvent)
				{
					oObject.detachEvent('on' + sEventName, fHandler);
					return true;
				}
			}
			return false;
		},

		process : function (oEvent)
		{
			var mX = null;
			var mY = null;
			if (null != oEvent.pageX)
			{
				var scroll = Utils.Screen.getScrollPosition();
				mX = oEvent.pageX - scroll.left;
				mY = oEvent.pageY - scroll.top;
			}
			else
			{
				mX = oEvent.clientX;
				mY = oEvent.clientY;
			}
			return {
				keyCode: oEvent.which ? oEvent.which : oEvent.keyCode,
				mouseX: mX,
				mouseY: mY,
				eventObject : oEvent
			}
		},

		cancel : function (oEvent)
		{
			if (oEvent.stopPropagation)
				oEvent.stopPropagation();
			else
				oEvent.cancelBubble = true;
			return oEvent;
		},
	
		preventDefault : function (oEvent)
		{
			if (oEvent.preventDefault)
				oEvent.preventDefault();
			else
				oEvent.returnValue = false;
			return oEvent;
		},
	
		kill : function (oEvent)
		{
			return oEvent = Utils.Events.cancel(Utils.Events.preventDefault(oEvent));
		}
	},

	Http :
	{
		createRequest : function ()
		{
			var ex;
			var h;
			try
			{
				h = new ActiveXObject('Msxml2.XMLHTTP');
			}
			catch (ex)
			{
				try
				{
					h = new ActiveXObject('Microsoft.XMLHTTP');
				}
				catch (ex)
				{
					try
					{
						h = new XMLHttpRequest();
					}
					catch (ex)
					{
						h = false;
					}
				}
			}
			return h;
		},
	
		isReady : function ( oRequest )
		{
			return oRequest
				&& (
					(4 == oRequest.readyState)
					|| ('complete' == oRequest.readyState)
				);
		},
	
		getResponseText : function ( oRequest )
		{
			var ret = (oRequest && oRequest.status && (200 == oRequest.status))
				? oRequest.responseText
				: false ;
			return ret;
		}	
	},
	
	Animation :
	{
		$ : [],
		
		MAXRESOLUTION : -1,
	
		Interpolations : {
			linear : function (nStep, nSteps)
			{
				return (0 < nSteps && nStep < nSteps) ? nStep / nSteps : 1;
			},
			
			exp2 : function (nStep, nSteps)
			{
				return (0 < nSteps)
					? Math.exp((1 - nStep / nSteps) * Math.log(1 / 2))
					: 1;
			},
			
			exp10 : function (nStep, nSteps)
			{
				return (0 < nSteps)
					? Math.exp((1 - nStep / nSteps) * Math.log(1 / 10))
					: 1;
			}
		},
		
		getState : function (nRatio, oCurrState, oNextState)
		{
			if (!oNextState) return oCurrState;
			var state = {};
			if (1 < nRatio) nRatio = 1;
			if (0 > nRatio) nRatio = 0;
			for (var i in oNextState)
			{
				if (null != oCurrState[i])
				{
					if ("opacity" == i)
					{
						state[i] = oCurrState[i] * (1 - nRatio) + oNextState[i] * nRatio;
						state.filter = 'alpha(opacity=' + Math.floor(100 * state[i]) + ')';
					}
					else if ("zIndex" == i)
					{
						state[i] = Math.round(oCurrState[i] * (1 - nRatio) + oNextState[i] * nRatio);
					}
					else
					{
						switch (typeof(oNextState[i]))
						{
							case 'object' :
								state[i] = Utils.Animation.getState(nRatio, oCurrState[i], oNextState[i]);
								break;
							case 'number' :
								state[i] = oCurrState[i] * (1 - nRatio) + oNextState[i] * nRatio;
								break;
							case 'string' :
								if ('#' == oNextState[i].charAt(0))
								{
									state[i] = Utils.Animation.getState(nRatio, new Color(oCurrState[i]), new Color(oNextState[i])).hex();
								}
								else if ('%' == oNextState[i].substr(oNextState[i].length - 1, 1))
								{
									var c = oCurrState[i].substr(0, oCurrState[i].length - 1) * 1;
									var n = oNextState[i].substr(0, oNextState[i].length - 1) * 1;
									state[i] = Math.round(c * (1 - nRatio) + n * nRatio) + '%';
								}
								else if ('px' == oNextState[i].substr(oNextState[i].length - 2, 2))
								{
									var c = oCurrState[i].substr(0, oCurrState[i].length - 2) * 1;
									var n = oNextState[i].substr(0, oNextState[i].length - 2) * 1;
									state[i] = Math.round(c * (1 - nRatio) + n * nRatio) + 'px';
								}
								else state[i] = oNextState[i];
								break;
							case 'function' :
							default :
								state[i] = oNextState[i];
								break;					
						}
					}
				}
			}
			return state;
		},
		
		getAnimationData : function (nAnimation)
		{
			return Utils.Animation.$[nAnimation] ? Utils.Animation.$[nAnimation] : false;
		},
	
		clear : function (nAnimation)
		{
			if (Utils.Animation.$[nAnimation] && Utils.Animation.$[nAnimation].timer) clearInterval(Utils.Animation.$[nAnimation].timer);
			Utils.Animation.$[nAnimation] = false;
		},
	
		stop : function (nAnimation)
		{
			var animation;
			if (animation = Utils.Animation.$[nAnimation])
			{
				clearInterval(animation.timer);
			}
		},
		
		isAnimating : function (nAnimation)
		{
			var animation = Utils.Animation.$[nAnimation];
			if (animation && animation.timer)
			{
				return true
			}
			else return false;
		},
		
		animate : function (nAnimation)
		{
			var animation = Utils.Animation.$[nAnimation];
			if (animation.busy) return;
			if (0 == animation.timeStamp) animation.timeStamp = (new Date()).getTime();
			animation.busy = true;
			if (animation && animation.keyFrames[animation.keyFrameIndex])
			{
				var res = animation.keyFrames[animation.keyFrameIndex].resolution;
				var steps;
				var step;
				if (1 > res)
				{
					var timestamp = (new Date()).getTime();
					steps = animation.keyFrames[animation.keyFrameIndex].runTime;
					step = timestamp - animation.timeStamp;
				}
				else
				{
					steps = res;
					step = animation.frameIndex;
					++animation.frameIndex;
				}
				var ratio = animation.keyFrames[animation.keyFrameIndex].interpolation(step, steps);
				var state = Utils.Animation.getState(ratio, animation.keyFrames[animation.keyFrameIndex].state, animation.keyFrames[animation.keyFrameIndex + 1] ? animation.keyFrames[animation.keyFrameIndex + 1] .state : false);
				Utils.Dom.setState(animation.object, state);
				if ((!animation.timer) && (animation.keyFrames[animation.keyFrameIndex + 1]))
				{
					animation.timer = setInterval(function () { Utils.Animation.animate(nAnimation); }, animation.keyFrames[animation.keyFrameIndex].interval);
				}
				if ((step >= steps) || (!animation.keyFrames[animation.keyFrameIndex + 1]))
				{
					animation.frameIndex = 0;
					clearInterval(animation.timer);
					if (animation.keyFrames[++animation.keyFrameIndex])
					{
						animation.timeStamp = (new Date()).getTime();
						animation.timer = setInterval(function () { Utils.Animation.animate(nAnimation); }, animation.keyFrames[animation.keyFrameIndex].interval);
					}
					else
					{
						if (animation.callback) animation.callback(nAnimation);
						Utils.Animation.clear(nAnimation);
						return;
					}
				}
				if (animation.keyFrames[animation.keyFrameIndex].callback) animation.keyFrames[animation.keyFrameIndex].callback(nAnimation);
				animation.busy = false;
			}
			else
			{
				if (animation.callback) animation.callback(nAnimation);
				Utils.Animation.clear(nAnimation);
			}
		},
	
		attach : function (oObject, aKeyFrames, fCallback)
		{
			oObject = $(oObject);
			if (oObject && aKeyFrames && aKeyFrames.length && (1 < aKeyFrames.length))
			{
				if (!fCallback) fCallback = false;
				for (var i = 0, l = Utils.Animation.$.length; ((i < l) && Utils.Animation.$[i]); ++i);
				Utils.Animation.$[i] = {
					object : oObject,
					keyFrames : aKeyFrames,
					keyFrameIndex : 0,
					frameIndex : 0,
					timeStamp : 0,
					callback : fCallback,
					timer : false,
					busy : false
				};
				return i;
			}
			else return false;
		}
	}
};

Math.dec2hex = function (dec, pad)
{
	var digits = '0123456789abcdef';
	var ret = '';
	dec = Math.floor(dec);
	while (0 < dec)
	{
		var m = dec % 16;
		dec -= m;
		dec = dec / 16;
		ret = digits.charAt(m) + ret;
	}
	if (pad && ((pad *= 1) > ret.length))
	{
		while (pad > ret.length)
		{
			ret = '0' + ret;
		}
	}
	return ret.length ? ret : '0';
}

Math.hex2dec = function (sHexadecimal)
{
	var digits = '0123456789abcdef';
	sHexadecimal += '';
	sHexadecimal = sHexadecimal.toLowerCase();
	var ret = 0;
	for (var i = 0, l = sHexadecimal.length; i < l; ++i)
	{
		var v = digits.indexOf(sHexadecimal.charAt(i));
		if (-1 < v)
		{
			ret *= 16;
			ret += v;
		}
		else i = sHexadecimal.length;
	}
	return ret;
}

function Color(sHexColor)
{
	if ('#' == (sHexColor = new String(sHexColor)).charAt(0)) sHexColor = sHexColor.substr(1);
	this.red = Math.hex2dec(sHexColor.substr(0, 2));
	this.green = Math.hex2dec(sHexColor.substr(2, 2));
	this.blue = Math.hex2dec(sHexColor.substr(4, 2));
}

Color.prototype.hex = function ()
{
	return '#' + Math.dec2hex(this.red, 2) + Math.dec2hex(this.green, 2) + Math.dec2hex(this.blue, 2);
}

Color.prototype.toString = function ()
{
	return this.hex();
}

function AnimationKeyFrame(oState, nRunTime, nResolution, fInterpolation, fCallback)
{
	if (!(nRunTime && (0 < nRunTime))) nRunTime = 3000;
	if (!fInterpolation) fInterpolation = Utils.Animation.Interpolations.linear;
	this.state = oState;
	this.runTime = nRunTime;
	if ((!nResolution) || (1 > nResolution))
	{
		this.interval = 20;
		this.resolution = Utils.Animation.MAXRESOLUTION;
	}
	else
	{
		this.resolution = nResolution;
		this.interval = Math.max(1, Math.floor(nRunTime / nResolution));
	}
	this.interpolation = fInterpolation;
	this.callback = ('function' == typeof(fCallback)) ? fCallback : false;
}

Date.prototype.formatAs = function (sFormat)
{
	var _n = this.getMonth() + 1;
	var _j = this.getDate();
	var _G = this.getHours();
	var _i = this.getMinutes();
	var _s = this.getSeconds();
	var date = {
		Y : this.getFullYear(),
		n : _n,
		m : ((_n < 10) ? '0' : '') + _n,
		j : _j,
		d : ((_j < 10) ? '0' : '') + _j,
		w : this.getDay(),
		G : _G,
		H : ((_G < 10) ? '0' : '') + _G,
		i : ((_i < 10) ? '0' : '') + _i,
		s : ((_s < 10) ? '0' : '') + _s
	};
	var result = new String(sFormat);
	for (var k in date)
	{
		result = result.replace(new String(k), date[k]);
	}
	return result;
}
