/**
                          )/_
                _.--..---"-,--c_
           \L..' Petrofied ._O__)_
   ,-.     _.+  _  \..--( /
     `\.-''__.-' \ (     \_
       `'''       `\__   /\
                   ')
*/
/**
 * Petro's Javascript Utility
 * Copyright © 2008 Petro Salema.
 * petro@petrosalema.com
 *
 * CHANGES:
 * Apr 08 2009 YOOL - Added hasClass
 *
 * TODO:
 * Add {Array}.pluck 2nd param to handle "prop"
 *
 * LAST MODIFIED:
 * April 08 2009
 */

var Petrofied = {
	version: "1.5.5"
};

Array.prototype.indexOf = function (needle, prop)
{ // If prop is specified, returns index of prop with value of needle in this Array if successful; otherwise -1

	var l = this.length;

	if (prop) {
		for (var i=0; i<l; i++) if (this[i][prop] === needle) return i;
	} else {
		for (var i=0; i<l; i++) if (this[i] === needle) return i;
	}

	return -1;
};

Array.prototype.pluck = function (needle)
{ // removes element needle from array and returns it
	var i = this.indexOf(needle);
	var r = this[i];
	this.splice(i, 1);
	return r;
}

Function.prototype.bind = function ()
{ // Give all functions the ability to return a clone of themselves bound the variable scope of "object"

	var args = Util.argsToArray(arguments);
	var object = args.shift();
	var method = this; 
	return function () { return method.apply(object, args.concat(Util.argsToArray(arguments))); }
};


Function.prototype.bindWithEvent = function ()
{ // Similar to "bind" but passes that the event object as first parameter

	var args = Util.argsToArray(arguments);
	var object = args.shift();
	var method = this; 
	return function (event) { return method.apply(object, [event || window.event].concat(args)); }
};

Number.prototype.bound = function (min, max)
{
	return Math.max((min || this), Math.min((max || this), this));
};

String.prototype.trim = function ()
{
	return this.replace(/^\s+/, '').replace(/\s+$/, '');
};

String.prototype.stripTags = function ()
{
	return this.replace(/<\/?[^>]+>/gi, '');
};

String.prototype.toCamelCase = function ()
{ // Will turn "mary had_a little-lamb" to "maryHadALittleLamb"

	var str = this;
	var chunks = str.replace(/-|_/g, ' ').replace(/\s+/g, ' ').split(' ');
	var camelized = chunks[0];

	for (var i=1, j=chunks.length; i<j; i++)
	{
		var s = chunks[i];
		camelized += s.substring(0, 1).toUpperCase() + s.substring(1, s.length);
	}

	return camelized;
};

String.prototype.toTitleCase = function ()
{ // Will turn "mary_had a Little-lamb" to "Mary_Had a Little-Lamb"

	var str = this;
	var odd = /([\s\-_])/g;
	var separator = "{-+toTitleChuckGap+-}";
	var chunks = str.replace(odd, "$1"+separator).split(separator);
	var ignore = ['to','it','on','the','a','and','or','nor','of','in'];
	var camelized = "";

	for (var i=0, j=chunks.length; i<j; i++)
	{
		var s = chunks[i];
		camelized += (ignore.indexOf(s.replace(odd, '')) == -1)
						? s.substring(0, 1).toUpperCase() + s.substring(1, s.length) : s;
	}

	return camelized;
};

function Class () // Creates classes for us
{
	var args = Util.argsToArray(arguments);
	var methods = [];

	// These arguments are Objects which we will combine into a single Object
	for (var i=0, j=args.length; i<j; i++)
		if (typeof args[i]  == 'object') methods.push(args[i]);

	function _constructor ()
	{
		// These are the arguments to be passed into the private _initialize function called on creation
		var argsForInit = Util.argsToArray(arguments);

		// Copy all our Objects' methods into _constructor
		for (var i=0, j=methods.length; i<j; i++)
			for (var key in methods[i])
				this[key] = methods[i][key];
			
		// Check if we have an _initialize function, if so call it thru "apply" and pass argsForInit to it
		if (this._initialize && typeof this._initialize == 'function')
			this._initialize.apply(this, argsForInit);
	}

	// Make sure we're using _constructor as our constructor
	_constructor.constructor = _constructor;

	return _constructor;
};

var EventListener = {
// Extends classes to enalbe event listening

	events: [],

	echoEvent: function (ev)
	{
		var e = this.events;
		for (var i in e)
			if (e[i].event == ev)
				e[i].callback();
	},

	addEvent: function (ev, fn)
	{ // Event is a String of the event name and fn is a Function

		if (!(ev && fn))
			return;

		// Prevent duplicate event and call pairs
		var e = this.events;
		for (var i in e)
			if (e[i].event == ev && e[i].callback == fn)
				return;

		//Make sure function "ev" exists on this object
		if (!(this[ev] && typeof(this[ev]) == 'function'))
			return;

		// Everything seems to be hunky-dory lets listen out for this event
		this.events.push({event:ev, callback:fn});

		var eventRename = 'event_' + ev;

		if (!this[eventRename]) {
			this[eventRename] = this[ev];

			var parent = this;

			this[ev] = function ()
			{
				var args = Util.argsToArray(arguments);
				parent[eventRename].apply(parent, args);
				parent.echoEvent(ev);
			}
		}
	}
};

// * * * * * * * * * * Util functions * * * * * * * * * *

// Create Util namespace
var Util = {};

Util.addClass = function (el, str)
{
	var rx = new RegExp("(^|\\s)" + str + "(\\s|$)", 'g');
	if (!rx.test(el.className))
		el.className += (el.className == '') ? str : " " + str;
};

Util.addEvent = function (el, ev, fn)
{ // Attaches Event Listener for Event ev to Element el

	if (!el)
		return;

	if (document.addEventListener)
		el.addEventListener(ev, fn, false);
	else if (document.attachEvent)
		el.attachEvent('on'+ev, fn);
};

Util.animateResizing = function (el, target_w, target_h, callback)
{
	var damp = 1.5;

	var offset_w = el.offsetWidth;
	var offset_h = el.offsetHeight;

	var tw = (target_w == null) ? offset_w : target_w;
	var th = (target_h == null) ? offset_h : target_h;

	var step_w = Math.round((tw - offset_w) / damp);
	var step_h = Math.round((th - offset_h) / damp);

	var measure = (target_w == null || target_w == 'auto') ? step_h : step_w;

	var w, h;


	if (Math.abs(measure) <= 2) {
		if (target_w == 'auto') { w = 'auto'; } else if (target_w != null) { w = tw + 'px'; }
		if (target_h == 'auto') { h = 'auto'; } else if (target_h != null) { h = th + 'px'; }

		TimeManagement.removeTimer(TimeManagment.getTimerId(arguments));

		if (callback)
			callback();
	} else {
		if (target_w == 'auto') { w = 'auto'; } else if (target_w != null) { w = (offset_w + step_w) + 'px'; }
		if (target_h == 'auto') { h = 'auto'; } else if (target_h != null) { h = (offset_h + step_h) + 'px'; }
	}

	if (target_w != null)
		el.style.width = w;
	if (target_h != null)
		el.style.height = h;
};

Util.argsToArray = function (args)
{
	return Array.prototype.slice.apply(args);
};

Util.createElement = function (tag, content, id, className)
{ // Creates and returns DOM element

	var el = document.createElement(tag);
	if (id)
		el.setAttribute('id', id);
	if (className)
		Util.addClass(el, className);
	el.innerHTML = content;

	return el;
}

Util.disableSelecting = function ()
{ // Disable selecting
	var el = document.body || document.getElementByTagName('body')[0];
	el.onselectstart = function () { return false; };
	el.unselectable = "on";
	el.style.MozUserSelect = "none";
	el.style.cursor = "default";
}

Util.enableSelecting = function ()
{ // Reenable selecting
	var el = document.body || document.getElementByTagName('body')[0];
	el.onselectstart = null;
	el.unselectable = "off";
	el.style.MozUserSelect = "";
	el.style.cursor = "auto";
}

Util.getChildAt = function (box, index)
{
	var child;
	while (child = box.childNodes[index])
	{
		if (child.nodeName == '#text')
			index++;
		else
			return child;
	}
}

Util.getElementsByClassName = function (name, parent, tags)
{ /* Returns an Array of DOMElements containing the className name
	 within the DOMElement parent (if specified).
	 May also be constrained to looking for elements of type tags if specified */

	var node = parent || document;
	var tags = tags ? tags.split(' ') : ['*'];
	var pattern = new RegExp('(^|\\s)' + name + '(\\s|$)');
	var elements = [], collection = [], results = [];

	while (tags.length > 0)
	{
		collection = node.getElementsByTagName(tags.shift());
		for (var i=0, l=collection.length; i<l; i++) elements.push(collection[i]);
	}

	for (var el in elements)
	{
		if (pattern.test(elements[el].className))
			results.push(elements[el]);
	}

	return results;
}

Util.getElementsByProperties = function (props, scope, tagName)
{ /* Returns parent Element of property el[k] = v */

	var test = false;
	var elements = (scope || document).getElementsByTagName(tagName || '*');
	var collection = [];

	for (var i=0, j=elements.length, el; i<j; i++)
	{
		el = elements[i];
		for (var k in props)
		{
			if (typeof(props[k]) == 'string')
				test = (el[k] == props[k]);
			else
				test = props[k].test(el[k]);
			if (!test) /* If even just one property is false then we might as well forget the rest */
				break;
		}
		if (test) /* if TRUE we've hit BULLZEYE */
			collection.push(el);
		else
			continue;
	}
	return collection;	
}

Util.getMouse = function (ev)
{ // Returns and Object containing the x and y values of the cursors position

	var x = 0, y = 0;

	if (!Util.isIE()) {
		x = ev.pageX;
		y = ev.pageY;
	} else {
		x = window.event.clientX;
		y = window.event.clientY + document.body.scrollTop;
	}

	return { x:x, y:y }
}

Util.getParent = function (el, props)
{ // Returns parent Element of property el[k] = v

	el = el.parentNode;
	var test = false;
	while (el.tagName != undefined)
	{
		for (var k in props)
		{
			if (typeof(props[k]) == 'string')
				test = (el[k] == props[k]);
			else
				test = props[k].test(el[k]);
			if (!test)
				break; /* If 1 property is false then we might as well forget the rest */
		}
		if (test)
			break; /* if TRUE we've hit BULLZEYE */
		else
			el = el.parentNode;
	}
	return ((el.tagName == undefined) ? null : el);	
}

Util.getSrc = function (e, match)
{ // Returns source Element of tagname tag for Event e
	var src = e.target || window.event.srcElement;
	
	if (!match)
		return src;
	
	var regexp = (typeof(match) != 'string');
	var tag = regexp ? match : match.toLowerCase();
	
	while (src.tagName != undefined)
	{
		if (regexp)
			if (match.test(src.tagName.toLowerCase()))
				break;
		
		if (src.tagName.toLowerCase() == tag)
			break;
		
		src = src.parentNode;
		
		if (!src)
			break;
	}
	
	if (!src)
		return null;
	else
		return ((src.tagName == undefined) ? null : src);
}

Util.getStyle = function (el, prop)
{ // Returns String of Element el's style Property prop

	var style = '';

	try {
		if (el.currentStyle) {
			prop = prop.toCamelCase();	// Must convert style-side to styleSide for IE
			style = el.currentStyle[prop];
		} else
			style = document.defaultView.getComputedStyle(el, null).getPropertyValue(prop);
	}
	catch(e) {}

	return style;
}

Util.getWindowScroll = function ()
{ // Returns an Object containing the window's x and y scroll values

	var x = 0, y = 0;

	if (typeof(window.pageYOffset) == 'number') {
		x = window.pageXOffset;
		y = window.pageYOffset;
	} else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
		x = document.body.scrollLeft;
		y = document.body.scrollTop;
	} else if (document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
		x = document.documentElement.scrollLeft;
		y = document.documentElement.scrollTop;
	}

	return { x:(x || 0), y:(y || 0) }
};

Util.getWindowSize = function ()
{ // Returns an Object containing the height and width of the browser window

	var w = 0, h = 0;

	if (self.innerHeight) {
		w = self.innerWidth;
		h = self.innerHeight;
	} else if (document.documentElement && document.documentElement.clientHeight) {
		w = document.documentElement.clientWidth;
		h = document.documentElement.clientHeight;
	} else if (document.body) {
		w = document.body.clientWidth;
		h = document.body.clientHeight;
	}

	return { width:w, height:h }
};

Util.globalOffset = function (element)
{ // Returns the real top and left values of element's position not its contextual value

	var l = 0, t = 0;

	while (element)
	{
		l += element.offsetLeft || 0;
		t += element.offsetTop  || 0;
		element = element.offsetParent;
	}

	return { l:l, t:t }
};

Util.hasClass = function (el, str)
{
	var rx = new RegExp("(^|\\s)" + str + "(\\s|$)", 'g');
	return rx.test(el.className);
};

Util.hitTest = function ()
{ /* Checks if object overlaps/intersects target. If three arguments are passed object's
     t and l will be arguments 0 and 1 and objects w and h will be 0 in order to present
     a pixel point as opposed to a box. */

	var a = arguments;

	var t = a[2] || a[1];
	var o = (a.length == 3) ? { l:a[0], t:a[1], w:0, h:0 } : a[0];

	var ot = Util.globalOffset(t);

	if (Util.isIE())
		ot.l -= parseInt(document.body.scrollLeft); 

	ot.w = t.offsetWidth;
	ot.h = t.offsetHeight;
	var bt = { l:ot.l, r:ot.l+ot.w, t:ot.t, b:ot.t+ot.h } // Target bounding box

	if (a.length == 3)
		var oo = o;
	else {
		var oo = Util.globalOffset(o);
		oo.w = o.offsetWidth;
		oo.h = o.offsetHeight;
	}

	var bo = { l:oo.l, r:oo.l+oo.w, t:oo.t, b:oo.t+oo.h } // Object bounding box

	return !!(bo.r > bt.l && bo.l < bt.r && bo.b > bt.t && bo.t < bt.b);
};

Util.insertAfter = function (node, sibling)
{
	if (sibling.nextSibling)
		sibling.parentNode.insertBefore(node, sibling.nextSibling);
	else
		sibling.parentNode.appendChild(node);
};

Util.isIE = function ()
{
	return !(document.getElementById && !document.all);
};

Util.json = function (data)
{
	var json = false;
	try { json = eval("("+data+")"); } catch(e) {}
	return json;
}

Util.removeEvent = function (el, type, fn)
{ // Removes Event Listener for Event type from Element el

	if (!el)
		return;

	if (document.addEventListener)
		el.removeEventListener(type, fn, false);
	else if (document.attachEvent)
		el.detachEvent('on'+type, fn);
};

Util.removeClass = function (el, str)
{
	var rx = new RegExp("(^|\\s)" + str + "(\\s|$)", 'g');
	el.className = el.className.replace(rx, "$1$2");
};

Util.removeNode = function (node)
{ // Removes DOM Node node and returns it

	var el = (typeof(node) == "string") ? document.getElementById(node) : node;
	el.parentNode.removeChild(el);
	return el;
};

Util.setStyle = function (el, prop, value)
{
	if (prop == 'alpha') {
		var a = parseFloat(value)
		el.style.opacity = '' + a;
		el.style.filter = 'alpha(opacity=' + (a * 100) + ')';
	} else
		el.style[Util.camelize(prop)] = value;
}

var_dump = function (obj, name)
{ /* For debugging(same as PHP function) */
	if (!/(object)|(array)/.test(typeof obj))
		return;
	var map = (name || "[Object]") + " {\n";
	for (var i in obj) map += "\t" + i + " => " + obj[i] + "\n";
	map += "}";
	return map;
}