/** 
 * == dom ==
 * DOM
 **/

/** 
 * == utilities ==
 * Utilities
 **/
if (typeof Prototype == 'undefined')
	throw new Error('Beezwax requires prototype.js');

document.head = document.head || document.getElementsByTagName('head')[0];

// make sure all console API functions are defined
(function() {
	if (!window.console) window.console = {};
	$w('log debug info warn error assert dir dirxml trace' +
	' group groupEnd time timeEnd profile profileEnd count').each(function(f) {
		window.console[f] = window.console[f] || Prototype.emptyFunction;
	})
})();

var Beezwax = (function() {
	/** section: dom
	 * class Beezwax.ElementList
	 * includes Enumerable, Element.Methods
	 * see http://gist.github.com/158849
	 */
	var ElementList = Class.create(Enumerable);
	Object.extend(ElementList.prototype, {
		initialize: function(selector) {
			this.elements = Object.isArray(selector) ? selector : $$(selector);
		},
		
		_each: function(iterator) {
			this.elements.each(iterator);
			return this;
		}
	});
 
	for (var methodName in Element.Methods) {
		ElementList.prototype[methodName] = (function(methodName) {
			return function() {
				var parameters = Array.prototype.slice.call(arguments, 0);
				this.elements.each(function(element) {
					element[methodName].apply(element, parameters);
				});
				return this;
			};
		})(methodName);
	}
	
	/** alias of: new Beezwax.ElementList(selector), section: dom
	 * $$$(selector) -> Beezwax.ElementList
	 * - selector (String):
	 */
	window.$$$ = function(selector) {
		return new ElementList(selector);
	}
	
	/** section: utilities
	 * Beezwax.load(scriptURL, callback) -> undefined
	 * - scriptURL (String): URL to the script to be loaded
	 * - callback (Function): executes after the script has fully loaded
	 */
	var loadedScripts = [];
	function load(scriptURL, callback) {
		if (loadedScripts.include(scriptURL)) {
			if (callback) callback();
			return Beezwax;
		}
		var script = $E('script', { src : scriptURL });
		if (callback) {
			script.observe(script.readyState ? 'readystatechange' : 'load',
				script.readyState ? function() {
					if (['loaded', 'complete'].include(script.readyState)) {
    	            	script.stopObserving('readystatechange');
    	            	callback();
					}
				} : callback
			);
    	}
		$(document.head).insert(script);
		loadedScripts.push(scriptURL);
		return Beezwax;
	};
	
	/** section: dom
	 * Element.data([key, fallback = null]) -> String | Object | null 
	 * - key (String): data attribute postfix key 
	 * - fallback (Object): returned if attribute value is not present
	 */
	Element.addMethods({
		data : function(element, key, fallback) {
			element = $(element);
			if (Object.isUndefined(key)) {
				if (element.dataset) return element.dataset;
				var data = {};
				$A(element.attributes).each(function(a) {
					if (a.name.startsWith('data-'))
						data[a.name.substring(5)] = a.value;
				});
				return data;
			}
			
			var value = element.readAttribute('data-' + key);
			if (value == null && !Object.isUndefined(fallback))
				return fallback;
			return value;
		}
	});
	return {
		ElementList : ElementList,
		load : load
	};
})();

/** section: dom
 * Beezwax.Behaviour
 **/
Beezwax.Behaviour = (function() {
	
	/** section: Beezwax
 	 * Element#delegate(@element, selector, eventName, handler) -> Element
	 **/
	Event.delegate = Event.delegate || function(element, selector, eventName, observer)  {
		element = $(element);
		if (eventName == 'blur' || eventName == 'focus')
			throw new Error('Event delegation for onBlur and onFocus events is not supported');
		else {
			Event.observe(element, eventName, function(event) {
				var child = event.element(), ancestors = [child].concat(child.ancestors());
				Selector.matchElements(ancestors, selector).each(function(element) {
					observer.bind(element)(event);
				});
			});
		}
		return element;
	};
	
	document.delegate = Event.delegate.methodize();
	Element.addMethods({ delegate: Event.delegate });
	
	return {
		
		/** section: Beezwax
		 * Beezwax.Behaviour.define(behaviours)
		 * - behaviours (Object)
		 **/
		define : function(behaviours) {
			var initialize = function(selector, callback) {
				document.observe('dom:loaded', function(e) {
					$$(selector).each(function(element) {
						callback(element);
					});
				});
			}
			
			$H(behaviours).each(function(behaviour) {
				if (Object.isFunction(behaviour.value))
					behaviour.value = { initialize : behaviour.value };
				$H(behaviour.value).each(function(specification) {
					if (specification.key == 'initialize') {
						initialize(behaviour.key, specification.value);
						return;
					}
					document.delegate(behaviour.key, specification.key, specification.value);
				});
			})
		}
	}
	
})();

Beezwax.Cookie = {
	
	COOKIE_TEMPLATE : "#{key}=#{json};expires=#{expiry};path=#{path};domain=#{domain}",
	
	// format cookie date string
	expiryDate : function(seconds) {
		return new Date(new Date().getTime() + seconds * 1000).toGMTString();
	},
	
	/**
 	 * Beezwax.Cookie.remove(key, value[, options]) -> Beezwax.Cookie
 	 * - key (String):
 	 * - value (Object | String | Number | Array):
 	 * - options (Object | Hash):
 	 **/
	set : function(key, value, options) { with(Beezwax.Cookie) {
		options = Object.extend({
			expiry : 3600 * 365,
			path : '/',
			domain : ''
		}, options || {});
		
		document.cookie = Beezwax.Cookie.COOKIE_TEMPLATE.interpolate({
			key : key,
			json : value != null ? window.escape(Object.toJSON(value)) : '',
			expiry : expiryDate(options.expiry),
			path : options.path,
			domain : options.domain
		});
		return Beezwax.Cookie;
	}},
	
	/**
 	 * Beezwax.Cookie.remove(key) -> Beezwax.Cookie
 	 * - key (String):
 	 **/
	remove : function(key) { with(Beezwax.Cookie) {
		set(key, null, { expiry : -3600 });
		return Beezwax.Cookie;
	}},
	
	/**
 	 * Beezwax.Cookie.get(key[, fallback]) -> Object
 	 * - key (String):
 	 * - fallback (Object):
 	 **/
	get : function(key, fallback) {
		fallback = fallback || null;
		try {
			var cookie = document.cookie.match(key + '=(.*?)(;|$)');
			return cookie ? window.unescape(cookie[1]).evalJSON() : fallback;
		} catch (e) { 
			return fallback; 
		}
	}
}

/**
 * Beezwax.Window.open(url[, options]) -> Window
 * - url (String):
 * - options (Object | Hash | undefined):
 **/
Beezwax.Window = {}, Beezwax.Window.open = function(url, options, name) {
	function translate(value) {
		if (typeof value == 'boolean')
			return value == true ? 'yes' : 'no';
		return value.toString();
	}
	name = name || '_blank';
	options = $H(Object.extend({
		status : false,
		toolbar : false, 
		location : false,
		menubar : false,
		directories : false,
		resizable : true,
		scrollbars : true,
		dependent : true,
		height : document.height,
		width : document.width,
		left : 0,
		top : 0
	}, options || { })).collect(function(option) {
		return option.key + '=' + translate(option.value);
	}).join(',');
	return window.open(url ? url : null, name, options);
}

/**
 * $E(elementName, attributes, contents) -> Element
 * $E(elementName, contents) -> Element
 * - elementName (String):
 * - attributes (Object | Hash):
 * - contents (String | Element | Array):
 **/
function $E(elementName, attributes, contents) {	
	if (Object.isArray(attributes) || Object.isString(attributes) || Object.isElement(attributes)) {
		contents = attributes;
		attributes = {};
	}
	var element = new Element(elementName, attributes);
	if (Object.isArray(contents))
		contents.each(function(c) { element.insert(c); });
	else
		element.insert(contents);
	return element;
}

Object.extend(Function.prototype, (function() {
	/**
	 * Function#delayed(seconds[, arguments...]) -> Function
	 * - seconds (Number):
 	 **/
	function delayed() {
		var f = this, parameters = arguments;
		return function() { f.delay.apply(f, parameters) };
	}
	return { delayed : delayed };
})());

Beezwax.Location = {
	GET : (function() {
		var GET = {}

		window.location.search.substring(1).split('&').each(function(part) {
			var keyandvalue = part.split('=');
			
			GET[window.decodeURIComponent(keyandvalue[0])] = window.decodeURIComponent(keyandvalue[1]);
		});
		
		GET.get = function(key, nullvalue) {
			return key in GET ? GET[key] : nullvalue || null;
		}
		return GET;
	})(),
	
	Hash : (function() {
		var hash = window.location.hash;
		var listener = null;
		function observe() {
			hash = window.location.hash;
			if (hash) fire(hash);			
			if (listener == null) {
				listener = new PeriodicalExecuter(function() {
					if (window.location.hash != hash) 
						fire(window.location.hash);
					hash = window.location.hash;
				}, .15);
			}
			return Beezwax.HashChange;
		}
		
		function fire(hash) {
			document.fire('hash:change', { hash : hash.substring(1) });
		}
		
		function stopObserving() {
			if (listener) listener.stop();
			listener = null;
			return Beezwax.HashChange;
		}
		
		return {
			observe : observe,
			stopObserving : stopObserving,
			toString : function() {
				return window.location.hash.toString().substring(1);
			}
		}
	}())
}

Beezwax.CSS = {
	equalizeHeights : function(elements) {
		var maxHeight = 0, height = 0;
		elements.invoke('setStyle', {'minHeight' : 'inherit'})
		elements.each(function(element) {
			height = element.getHeight();
			if (height > maxHeight)
				maxHeight = height;
		}).invoke('setStyle', { minHeight : maxHeight + 'px' });
	},
	
	value : function(value) {
		var matches = /^(\d+)\s*(?:px|em|%)$/.exec(value);
		if (matches)
			return window.parseInt(matches[1]);
		return null;
	}
}

Function.IDENTITY = Function.IDENTITY || function(x) { return x; }
Function.EMPTY = Function.EMPTY || function() { }
