/**
 * Petro's Javascript Utility v1
 * Copyright © 2008 Petro Salema.
 * petro@petrosalema.com
 */


var Petrofied = {

	version: 1.0

}


Array.prototype.indexOf = function(needle)
{
	for(var i=0, l=this.length; i<l; i++) { if(this[i] === needle) return i; }
	return -1;
}


Function.prototype.bindTo = 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.bindWithEventTo = function()
{/* Similar to "bindTo" 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)
{
	if(!min) min = this;
	if(!max) max = this;

	return Math.max(min, Math.min(max, this));
}


String.prototype.strip = function()
{
	return this.replace(/^\s+/, '').replace(/\s+$/, '');
}


String.prototype.stripTags = function()
{
	return this.replace(/<\/?[^>]+>/gi, '');
}


String.prototype.camelcase = 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.titlecase = 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;
}


function Class()
{/* methods are functions which we want to attach to our class
    these functions are contained in an Object we pass as the
    first argument of our Class constructor */

	var argset = Util.argsToArray(arguments);

	var methods = [];

	for(var i=0, j=argset.length; i<j; i++)
	{
		if(typeof(argset[i]) == 'object')
		{
			methods.push(argset[i]);
		} 
	}

	function ClassConstruct()
	{/* ClassConstruct is a constuct of our Class based on the aforementioned "methods" passed to Class.
	    We first map all the properties of "methods" to it. We then apply the now attached function init
	    by passing the n number of arguments supplied to ClassConstruct in the creation of an instance */

		var args = Util.argsToArray(arguments);

		for(var i=0, j=methods.length; i<j; i++)
		{
			for(var key in methods[i]) { this[key] = methods[i][key]; }
		}

		if(!!(this.init && typeof(this.init) == 'function'))
		{/* Make sure we have an init function attached, then pass args to it FOR LEGACY TO BE REMOVED*/

			this.init.apply(this, args);
		}
		else if(!!(this._init && typeof(this._init) == 'function'))
		{/* Make sure we have an init function attached, then pass args to it */

			this._init.apply(this, args);
		}
	}

	// Make sure we use ClassConstructor as out constructor 
	ClassConstruct.constructor = ClassConstruct;

	return ClassConstruct;
}

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);
			}
		}
	}
}




var TimeManagement = {

	timers: [],

	last_timer_id: 0,

	createTimer: function()
	{
		var args = Util.argsToArray(arguments);
		var call = args.shift();
		var interval = args.shift();

		this.last_timer_id++;

		var timer_id = 'timer_identifier_' + this.last_timer_id;

		var fn = function() { return call.apply({}, args.concat([timer_id])); }
		/* This means that the very last argument of any timed event will be it's timer id */

		//this.removeTimer(call);
		this.timers.push({ id: timer_id, timer: setInterval(fn, interval) });
	},

	removeTimer: function(id)
	{
		var t = this.timers;

		for(var i in t)
		{
			if(t[i].id == id)
			{
				clearInterval(t[i].timer);
				t[i].timer = null;
				t.splice(i, 1);
			}
		}

		trace(id);
	},


	getTimerId: function(args)
	{
		var a = Util.argsToArray(args);
		var timer_id = a[a.length - 1];

		if(/^timer_identifier_[\d]+$/.test(timer_id)) return timer_id;

		return false;
	}

}





var Util = { }


Util.addEvent = function(el, type, fn)
{/* Attaches Event Listener for Event type to Element el */

	if(!el) { return; }

	if(document.addEventListener)
	{
		el.addEventListener(type, fn, false);
	}
	else if (document.attachEvent)
	{
		el.attachEvent('on'+type, fn);
	}
}


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.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'; }
	}

	trace(h, false);

	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.camelize = function(str)
{/* Will turn "mary had_a little-lamb" to "maryHadALittleLamb" */

	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;
}


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.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 tag if tag is 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.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.getStyle = function(el, prop)
{/* Returns String of Element el's style Property prop */

	var style = '';

	try {
		if(el.currentStyle)
		{
			prop = Util.camelize(prop);	// 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;
	}

	x = x || 0;
	y = y || 0;

	return { x:x, y:y }
}


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.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.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, "");
}


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;
	}
}