/**
 * @see Prototype.js
 * prototype_ss.js 
 * Requires: Prototype.js 1.6.0
 */
 
/* Handy Objects
--------------------------------------------------------------------------*/
var Cookie = {
	/**
	 * Gets the value of a cookie by name
	 *
	 * @param String name
	 * @return String
	 */
	get: function(name) {
		var val = String(document.cookie).replace(new RegExp("^(.*; )?"+name+"\=([^;]*)(;)?.*$"), "$2");
		return (val == document.cookie)? null : unescape(val);
	},
	/**
	 * Set the value of a cookie by name
	 *
	 * @param String name
	 * @param String value
	 * @param Date expDate
	 * @return void
	 */
	set: function(name, value, expDate) {
		document.cookie = name+"="+escape(value) + ";expires="+expDate.toGMTString();
	},
	/**
	 * Removes a cookie by name
	 *
	 * @param String name
	 * @return void
	 */
	remove: function(name) {
		Cookie.set(name, '', new Date(Date.parse('Thu, 01-Jan-1970 00:00:01 GMT')));
	}
};

/* String.prototype
---------------------------------------*/
Object.extend(String.prototype, {
	/**
	 * Extends the .substring() method to allow negative numbers to
	 * reference indices from the end of the string rather than the beginning.
	 *
	 * @param Number start
	 * @param Number end
	 * @return String
	 */
	substring: String.prototype.substring.wrap(
		function(proceed, start, end) {
			if (start < 0) start = this.length + start;
			if (end < 0) end = this.length + end;
			if (end !== 0 && !end) end = this.length;
			return proceed(start, end);
		}
	),
	
	/**
	 * Turns a plain string in to DOM Nodes
	 *
	 * @return Array
	 */
	toDOMNodes: function() {
		return new Element('div').update(this).childElements();
	}
});

Object.extend(Object, {
	/**
	 * Is Numeric
	 * checks if an object is a Number or String of just numbers
	 *
	 * @param unknown object
	 * @return boolean
	 */
	isNumeric: function(object) {
		if(parseInt(object) == object)
			return true;
		return false;
	},
	/**
	 * Converts a UNIX timestamp into a JS Date object
	 *
	 * @param unknown object
	 * @return Date
	 */
	toDate: function (object) {
		var time = new Date();
		time.setTime(parseInt(object)*1000);
		return time;
	},
	/**
	 * Extends the Object.extend function to allow it to be recursive
	 * 
	 * @param Object destination
	 * @param Object source
	 * @param bool recursive
	 * @return Object
	 */
	isObject: function(object) {
		if (object && object.toString() == "[object Object]") {
			return true;
		} else {
			return false;
		}
	},
	/**
	 * @return null
	 */
	isValidDate: function(date) {
		return (date instanceof Date && date.toString() != 'Invalid Date' && date.toString() != 'NaN');
	},
	
	extend: function(destination, source, recursive) {
		if (recursive !== true) {
			for (var property in source)
				destination[property] = source[property];
		} else if (Object.isObject(source)) {
			if (destination) {
				for (var property in source) {
					if (destination[property]) {
						destination[property] = Object.extend(destination[property], source[property], recursive);
					} else {
						destination[property] = Object.extend({}, source[property], recursive);
					}
				}
			} else {
				destination = Object.extend({}, source);
			}
		} else if (Object.isArray(source)) {
			destination = [];
			for (var i=0, length=source.length; i<length; i++)
				destination[i] = Object.extend({}, source[i], recursive);
		} else {
			destination = source;
		}
		return destination;
	},
	
	/**
	 * Extends the Object.clone function to use recursive extension
	 * @param Object object
	 * @return Object
	 */
	clone: function(object, recursive) {
		return Object.extend({}, object, recursive);
	}
});

/* Class
---------------------------------------*/
/**
 * Extends the Class.Methods.addMethods function to make it recursively add settings
 * @param Function source
 * @return null
 */

Class.Methods.addMethods = function(source) {
	var ancestor   = this.superclass && this.superclass.prototype;
	var properties = Object.keys(source);
	
	if (!Object.keys({ toString: true }).length)
		properties.push("toString", "valueOf");
	
	for (var i = 0, length = properties.length; i < length; i++) {
		if(typeof(source) == "undefined") return;
		var property = properties[i], value = source[property];
		if (ancestor && Object.isFunction(value) &&
		value.argumentNames().first() == "$super") {
			var method = value, value = Object.extend((function(m) {
				return function() { return ancestor[m].apply(this, arguments) };
			})(property).wrap(method), {
				valueOf:  function() { return method },
				toString: function() { return method.toString() }
			});
		}
		if (Object.isObject(value)) {
			this.prototype[property] = Object.extend(Object.clone(this.prototype[property]), value, true);
		} else {
			this.prototype[property] = value;
		}
	}
	
	return this;
}

/* Element
---------------------------------------*/
Object.extend(Element.Methods, {
	/**
	 * Finds if the element or any parent element has a CSS display of none
	 *
	 * @param DOMElement element
	 * @return bool
	 */
	displayed: function(element) {
		element = $(element);
		return [element].concat(element.ancestors()).all(function(element){
			if(['none',null].indexOf($(element).getStyle('display')) == -1)
				return true;
		});
	},
	/**
	 * Finds the highest z-index of the current element or any parent element
	 *
	 * @param DOMElement element
	 * @return Number
	 */
	positionedZIndex: function (element) {
		element = $(element);
		return [element].concat(element.ancestors()).max(function(v){
			return v.getStyle('zIndex')
		});
	},
	
	/**
	 * Parses the classname looking for a JSON string and returns the object if it is found. returns false if no valid JSON string is found in the class
	 *
	 * @param DOMElement element
	 * @return Object
	 */
	getClassObject: function (element) {
		var vals = String(element.className).split(' ');
		for(var x=0, len=vals.length; x<len; x++){
			if(vals[x].isJSON()){
				return vals[x].evalJSON();
			}
		}
		return false;
	}
});

/* Event
---------------------------------------*/
//Event._observe = Event.observe;
Object.extend(Event, {

	/**
	 * Event.observe now returns an ID which can be sent to Event.stopObserving to terminate that event handler.
	 * @var Array
	 */
	registry: [],
	
	/**
	 * What is this for?
	 * @var int
	 */
	id: 0,
	
	/**
	 * Returns the keycode related to an event, browser-independent
	 * @param DOMEvent e
	 * @return int
	 */
	keyCode: function(e) {
		if (typeof(e.which) == 'undefined') return e.keyCode;
		return Math.max(e.which, e.keyCode);
	},
	
	/**
	 * Fires the event in the given context
	 * @param DOMEvent e
	 * @param DOMElement element
	 * @return int
	 */
	dispatch: function(e, element) {
		element = $(element);
		if (!element.dispatchEvent) {
			var type = e.type;
			return element.fireEvent('on' + type, e);
		} else {
			// TODO: this doesn't work in FireFox for some reason
			return element.dispatchEvent(e);
		}
	},

	/**
	 * @param DOMElement element
	 * @param string eventName
	 * @param function handler
	 * @param bool useCapture [optional]
	 * @return object
	 */
	observe: Event.observe.wrap(
		function(proceed, element, name, observer, useCapture) {
			var regEntry = {
				id: Event.id++,
				element: element,
				name: name,
				observer: observer,
				useCapture: useCapture
			};
			Event.registry.push(regEntry);
			proceed(element, name, observer, useCapture);
			return regEntry;
		}
	),

	/**
	 * Uses the registry to stop opserving a specific event
	 * @param object regEntry
	 * @return bool
	 */
	unObserve: Event.stopObserving.wrap(
		function(proceed, regEntry) {
			Event.registry.find(function(eve, key){
				if(eve.id == regEntry.id){
					Event.registry.splice(key, 1);
					console.log(eve);
					proceed(eve.element, eve.name, eve.handler, eve.useCapture);
					return true;
				}
			});
		}
	),
	
	/**
	 * Uses the registry to stop opserving all events applied to an element
	 * @param DOMElement element
	 * @param string eventName [optional]
	 * @return true
	 */
	stopObservingAll: Event.stopObserving.wrap(
		function(proceed, element, eventName) {
			if(typeof(eventName) == 'undefined') eventName = null;
			if(typeof(element) == 'undefined') element = null;
			Event.registry.each(function(eve){
				if (!eve ||
					(eventName != null && eve.name != eventName) ||
					(element != null && eve.element != element))
					return;
				Event.registry.splice(x, 1);
				proceed(eve.element, eve.name, eve.handler, eve.useCapture);
			});
			return true;
		}
	),
	
	/**
	 * @param DOMElement el
	 * @param String name
	 * @return void
	 */
	fire: function(el, name) {
		Event.registry.each(function(eve){
			if(eve.element == el && eve.name == name){
				eve.observer();
				throw $break;
			}
		});
	}
});
Element.addMethods({
	observe: Event.observe,
	stopObservingAll: Event.stopObservingAll
});
Object.extend(document, {
  observe:       Element.Methods.observe.methodize(),
  stopObservingAll: Element.Methods.stopObservingAll.methodize()
});

/* window
---------------------------------------*/
Object.extend(window, {
	/* Extend the alert function to properly alert objects */
	shout: function(msg){
		if(!this.num) this.num = 0;
		if(typeof(this.showAlerts) == 'undefined') this.showAlerts = true;
		if(!this.showAlerts){
			return false;
		}
		this.num++;
		if(this.num%10 == 0){
			this.showAlerts = confirm("Ok to Continue; Cancel to stop alerts until page refresh");
		}
		if(!this.showAlerts){
			return false;
		}
		if(msg instanceof Object){
			var html = msg+"\n--------------------\nKEY:VALUE\n--------------------\n";
			for(var key in msg){
				html += key + ":" + msg[key] + "\n";
			}
			alert(html);
		}else{
			alert(msg);
		}
	}
});

/* document.viewport
---------------------------------------*/
Object.extend(document.viewport, {
	/**
	 * Updated function from the prototype site to make it work properly
	 * @return Array
	 */
	getDimensions: function() {
		var dimensions = { }; 
		$w('width height').each(function(d) { 
			var D = d.capitalize();
			if (Prototype.Browser.Opera || document.documentElement.clientHeight == 0 || // IE quirksmode 
			Prototype.Browser.Gecko && !(document.body.clientHeight == document.body.offsetHeight &&  
			document.body.clientHeight == document.body.scrollHeight)) // FF quirksmode 
				dimensions[d] = document.body['client' + D]; 
			else if (Prototype.Browser.WebKit) dimensions[d] = self['inner' + D]; 
			else dimensions[d] = document.documentElement['client' + D]; 
		}); 
		return dimensions; 
	}
});
