String.extend({
	toNumber: function() {
		return this.replace(/[^0-9]/g, '').toInt();
	},

	camelcase: function() {
		return this.replace(/\_+(.)?/g, function(match, char) { return (char || '').toUpperCase() });
	},

	replaceAll: function(from, to) {
		return this.replace(new RegExp(from, "gi"), to);
	},

	/*
	Property: numberFormat
		Format a number with grouped thousands.

	Arguments:
		decimals, optional - integer, number of decimal percision; default, 2
		dec_point, optional - string, decimal point notation; default, '.'
		thousands_sep, optional - string, grouped thousands notation; default, ','

	Returns:
		a formatted version of number.

	Example:
		>(36432.556).numberFormat()  // returns 36,432.56
		>(36432.556).numberFormat(2, '.', ',')  // returns 36,432.56
	*/

	numberFormat : function(decimals, dec_point, thousands_sep) {
		decimals = Math.abs(decimals) + 1 ? decimals : 2;
		dec_point = dec_point || ',';
		thousands_sep = thousands_sep || '.';

		var matches = /(-)?(\d+)(\.\d+)?/.exec((isNaN(this) ? 0 : this) + ''); // returns matches[1] as sign, matches[2] as numbers and matches[3] as decimals
		var remainder = matches[2].length > 3 ? matches[2].length % 3 : 0;
		return (matches[1] ? matches[1] : '') + (remainder ? matches[2].substr(0, remainder) + thousands_sep : '') + matches[2].substr(remainder).replace(/(\d{3})(?=\d)/g, "$1" + thousands_sep) +
				(decimals ? dec_point + (+matches[3] || 0).toFixed(decimals).substr(2) : '');
	},

	currencyFormat: function() {
		return '&euro; ' + String.numberFormat(this);
	}
});

Number.extend({
	numberFormat : function(decimals, dec_point, thousands_sep) {
		return String.numberFormat(this, decimals, dec_point, thousands_sep);
	},

	currencyFormat: function() {
		return String.currencyFormat(this);
	}
});

Element.extend({
	getWidth : function() {
		// TODO: cache element size to avoid another getSize() call
		size = (this.__size) ? this.__size : this.getSize();
		return size['size']['x'];
	},

	getHeight: function() {
		size = (this.__size) ? this.__size : this.getSize();
		return size['size']['y'];
	},

	center: function(el) {
		if (!el) el = window;

		this.setStyles({
			'top': (el.getHeight() - this.getHeight()) / 2 + window.getScrollTop(),
			'left': (el.getWidth() - this.getWidth()) / 2  + window.getScrollLeft(),
			'disaplay': 'block',
			'position': 'absolute',
			'z-index': 25
		});

		return this;
	},

	hide: function() {
		this.setStyle('display', 'none');
		this.addClass('hidden');
		return this;
	},

	show: function() {
		this.setStyle('display', 'block');
		this.removeClass('hidden');
		return this;
	},

	toggle: function() {
		if (this.getStyle('display') == 'none')
			this.show();
		else
			this.hide();

		return this;
	},

	getParents: function(selector) {
		return $$(selector || '').filter(function(el) {
			return (el.hasChild(this));
		}, this).reverse();
	},

	getParent: function(selector) {
		if (selector) {
			return $(this.getParents(selector)[0]);
		} else {
			return $(this.parentNode);
		}
	},

	findParent: function(tag) {
		var el = this.getParent();
		while (el.getTag() != tag) {
			if (!el.getParent) {
				el = false;
				break;
			}
			el = el.getParent();
		}

		return el;
	},

	positionCursor: function(char) {
		if (this.setSelectionRange) {
			this.focus();
			this.setSelectionRange(char, char);
		} else if (this.createTextRange) {
			var range = this.createTextRange();
			range.collapse(true);
			range.moveEnd('character', char - 1);
			range.moveStart('character', char - 1);
			range.select();
			this.focus();
		}
	},

	addOption: function(value, title) {
		try {
			this.add(new Option(title, value), null);
		} catch (e) {
			this.add(new Option(title, value));
		}
	},

	fix: function() {

		if (!window.ie6) {
			return this;
		}

		var img;
		var tag = this.getTag();

		if (tag == 'img') {
			img = "'" + this.getProperty('src') + "'";
			this.setProperty('src', 'null.gif');
		} else {
			var bg = this.getStyle('background-image');
			if (bg && bg != 'none') {
				img = bg.match(/\(([^)]+)\)/)[1];
			}
		}

		if (img) {
//			if (this.getStyle('display') == 'inline' && !['input', 'textarea', 'button'].contains(tag)) {
//				this.setStyles({
//					'display': 'block',
//					'width': this.getStyle('width')
//				});
//			}

			this.setStyles({
				'background': '',
				'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled='true', src=" + img + ", sizingMethod='crop')"
			});
		}

		return this;
	}
});


var Template = {
	init: function() {},

	parse: function(template, data) {
		var el = $(template);
		if (!el) {
			return null;
		}

		var html = el.textContent || el.value;

		for (key in data) {
			var replace = "{" + key + "}".escapeRegExp();
			html = html.replaceAll(replace, data[key]);
		}

		var tmp = new Element('div').setHTML(html);

		return tmp.getFirst();
	}
};
Template.init.bind(Template);


var Complete = new Class({
	options: {
		'delay': 500,
		'className': 'complete',
		'minLength': 1,
		'preload': false,
		'left': -3,
		'top': 0,
		'onSelect': false
	},

	cache: {},

	initialize: function(el, options) {
		this.setOptions(options);

		this.el = $(el);
		if (!this.el) {
			return false;
		}

		this.hidden = $(this.el.id + 'Id');
		this.value = this.el.value;

		if (this.options.preload) {
			this.cache = this.options.preload;
		}

		this.progress = new Element('div', {'class': 'progress'}).setStyle('display', 'none');
		this.progress.injectAfter(this.el);

		this.ajax = new Ajax(this.options.url, {
			'method': 'get',
			'autoCancel': true,
			'onComplete': this.updateComplete.bind(this)
		});

		this.menu = new Element('ul', {'class': this.options.className})
			.setStyles({
				'z-index': 10,
				'display': 'none'
			})
			.addEvents({
				'click': this.select.bindWithEvent(this),
				'mouseover': this.hover,
				'mouseout': this.hover
			})
			.injectInside(document.body);

		this.fix = new OverlayFix(this.menu);

		this.el.setProperty('autocomplete', 'off')
			.addEvent('keydown', this.onInput.bind(this));
//			.addEvent('blur', this.hide.create({'delay': 50, 'bind': this}));
	},

	show: function() {
		var coords = this.el.getCoordinates();
		this.menu.setStyles({
			'left': coords.left + this.options.left,
			'top': coords.top + coords.height + this.options.top,
			'display': 'block'
		});

		this.fix.show();
	},

	hide: function(e) {
		this.menu.setStyle('display', 'none');
		this.fix.hide();
		this.progress.setStyle('display', 'none');
		this.ajax.cancel();
	},

	hover: function(e) {
		if (this.hovered) {
			this.hovered.removeClass('selected');
		}

		if ($type(e) == 'element') {
			this.hovered = e.addClass('selected');
		} else if (e.type == 'mouseover') {
			e = new Event(e);
			this.hovered = e.target.addClass('selected');
		}
	},

	select: function(e) {
		e = ($type(e) == 'element') ? e : e.target;
		this.value = this.el.value = e.getText();

		if (this.hidden && e._id) {
			this.hidden.value = e._id;
		}

		this.el.blur();
		this.hide();

		if ($type(window[this.options.onSelect]) == 'function') {
			window[this.options.onSelect].call();
		}
	},

	update: function(e) {
		this.lookup = this.el.value.toLowerCase();
		if (this.lookup.length == 0) {
			return false;
		}

		this.ajax.options['data'] = {'lookup': this.lookup};
		this.ajax.request();
		this.progress.setStyle('display', 'block');
	},

	updateComplete: function(resp) {
		this.progress.setStyle('display', 'none');

		var data = Json.evaluate(resp);
		if (data.length == 0) {
			this.hide();
			return false;
		}

		this.cache[this.lookup] = data;
		this.build(data);
	},

	build: function(data) {
		if (data.length == 0) {
			this.hide();
			return false;
		}

		this.menu.setHTML('');
		for (id in data) {
			var item = new Element('li').setHTML(data[id]);
			item._id = id;
			this.menu.adopt(item);
		}
		this.menu.getLast().addClass('last');
		this.hover(this.menu.getFirst());
		this.show();
	},

	onInput: function(e) {
		var el;
		e = new Event(e);

		switch (e.key) {
			case 'up':
				el = (this.hovered) ? this.hovered.getPrevious() : false;
				el = el || this.menu.getLast();
				this.hover(el);
			break;
			case 'down':
				el = (this.hovered) ? this.hovered.getNext() : false;
				el = el || this.menu.getFirst();
				this.hover(el);
			break;
			case 'enter':
			case 'tab':
				e.stop();
				this.select(this.hovered);
				return false;
			break;
			case 'esc':
				this.hide();
			break;
		}

		if (e.key.length != 1 && !['delete', 'backspace'].contains(e.key)) {
			return false;
		}

		if (this.hidden) {
			this.hidden.value = null;
		}

		this.onChange.delay(20, this);
	},

	onChange: function() {
		if (this.value == this.el.value || this.el.value.length < this.options.minLength) {
			return false;
		}

		this.value = this.el.value;
		var lookup = this.value.toLowerCase();

		if (this.cache[lookup]) {
			this.build(this.cache[lookup]);
			return false;
		}

		$clear(this.timeout || null);
		this.timeout = this.update.delay(this.options.delay, this);
	}
});
Complete.implement(new Options, new Events);


var OverlayFix = new Class({

	initialize: function(el) {
		if (window.ie6) {
			this.element = $(el);
			this.relative = this.element.getOffsetParent();
			this.fix = new Element('iframe', {
				'frameborder': '0',
				'scrolling': 'no',
				'src': 'javascript:false;',
				'styles': {
					'position': 'absolute',
					'border': 'none',
					'display': 'none',
					'filter': 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
				}
			}).inject(this.element, 'after');
		}
	},

	show: function() {
		if (this.fix) {
			var coords = this.element.getCoordinates(this.relative);
			delete coords.right;
			delete coords.bottom;
			this.fix.setStyles($extend(coords, {
				'display': '',
				'zIndex': (this.element.getStyle('zIndex') || 1) - 1
			}));
		}
		return this;
	},

	hide: function() {
		if (this.fix) this.fix.setStyle('display', 'none');
		return this;
	},

	destroy: function() {
		if (this.fix) this.fix = this.fix.destroy();
	}
});


var Browser = $merge({

	Engine: {name: 'unknown', version: 0},

	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

	Plugins: {},

	Engines: {

		presto: function(){
			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
		},

		trident: function(){
			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4);
		},

		webkit: function(){
			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
		},

		gecko: function(){
			return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18);
		}

	}

}, Browser || {});

Browser.Platform[Browser.Platform.name] = true;

Browser.detect = function(){

	for (var engine in this.Engines){
		var version = this.Engines[engine]();
		if (version){
			this.Engine = {name: engine, version: version};
			this.Engine[engine] = this.Engine[engine + version] = true;
			break;
		}
	}

	return {name: engine, version: version};

};

Browser.detect();


Element.implement({
	getDocument: function(){
		return this.ownerDocument;
	},

	getComputedStyle: function(property){
		if (this.currentStyle) return this.currentStyle[property.camelCase()];
		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
	},

	scrollTo: function(x, y){
		if (isBody(this)){
			this.getWindow().scrollTo(x, y);
		} else {
			this.scrollLeft = x;
			this.scrollTop = y;
		}
		return this;
	},

	getSize: function(){
		if (isBody(this)) return this.getWindow().getSize();
		return {x: this.offsetWidth, y: this.offsetHeight};
	},

	getScrollSize: function(){
		if (isBody(this)) return this.getWindow().getScrollSize();
		return {x: this.scrollWidth, y: this.scrollHeight};
	},

	getScroll: function(){
		if (isBody(this)) return this.getWindow().getScroll();
		return {x: this.scrollLeft, y: this.scrollTop};
	},

	getScrolls: function(){
		var element = this, position = {x: 0, y: 0};
		while (element && !isBody(element)){
			position.x += element.scrollLeft;
			position.y += element.scrollTop;
			element = element.parentNode;
		}
		return position;
	},

	getOffsetParent: function(){
		var element = this;
		if (isBody(element)) return null;
		if (!Browser.Engine.trident) return element.offsetParent;
		while ((element = element.parentNode) && !isBody(element)){
			if (element.getComputedStyle('position') != 'static') return element;
		}
		return null;
	},

	getOffsets: function(){
		if (Browser.Engine.trident){
			var bound = this.getBoundingClientRect(), html = this.getDocument().documentElement;
			var isFixed = this.getComputedStyle('position') == 'fixed';
			return {
				x: bound.left + ((isFixed) ? 0 : html.scrollLeft) - html.clientLeft,
				y: bound.top +  ((isFixed) ? 0 : html.scrollTop)  - html.clientTop
			};
		}

		var element = this, position = {x: 0, y: 0};
		if (isBody(this)) return position;

		while (element && !isBody(element)){
			position.x += element.offsetLeft;
			position.y += element.offsetTop;

			if (Browser.Engine.gecko){
				if (!borderBox(element)){
					position.x += leftBorder(element);
					position.y += topBorder(element);
				}
				var parent = element.parentNode;
				if (parent && parent.getComputedStyle('overflow') != 'visible'){
					position.x += leftBorder(parent);
					position.y += topBorder(parent);
				}
			} else if (element != this && Browser.Engine.webkit){
				position.x += leftBorder(element);
				position.y += topBorder(element);
			}

			element = element.offsetParent;
		}
		if (Browser.Engine.gecko && !borderBox(this)){
			position.x -= leftBorder(this);
			position.y -= topBorder(this);
		}
		return position;
	},

	getPosition: function(relative){
		if (isBody(this)) return {x: 0, y: 0};
		var offset = this.getOffsets(), scroll = this.getScrolls();
		var position = {x: offset.x - scroll.x, y: offset.y - scroll.y};
		var relativePosition = (relative && (relative = $(relative))) ? relative.getPosition() : {x: 0, y: 0};
		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
	},

	getCoordinates: function(element){
		if (isBody(this)) return this.getWindow().getCoordinates();
		var position = this.getPosition(element), size = this.getSize();
		var obj = {left: position.x, top: position.y, width: size.x, height: size.y};
		obj.right = obj.left + obj.width;
		obj.bottom = obj.top + obj.height;
		return obj;
	},

	computePosition: function(obj){
		return {left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top')};
	},

	position: function(obj){
		return this.setStyles(this.computePosition(obj));
	}
});


var Accordion = Fx.Elements.extend({

	options: {
		onActive: Class.empty,
		onBackground: Class.empty,
		display: 0,
		show: false,
		height: true,
		width: false,
		opacity: true,
		fixedHeight: false,
		fixedWidth: false,
		wait: false,
		alwaysHide: false
	},

	initialize: function(){
		var options, togglers, elements, container;
		$each(arguments, function(argument, i){
			switch($type(argument)){
				case 'object': options = argument; break;
				case 'element': container = $(argument); break;
				default:
					var temp = $$(argument);
					if (!togglers) togglers = temp;
					else elements = temp;
			}
		});
		this.togglers = togglers || [];
		this.elements = elements || [];
		this.container = $(container);
		this.setOptions(options);
		this.previous = -1;
		if (this.options.alwaysHide) this.options.wait = true;
		if ($chk(this.options.show)){
			this.options.display = false;
			this.previous = this.options.show;
		}
		if (this.options.start){
			this.options.display = false;
			this.options.show = false;
		}
		this.effects = {};
		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
		this.elements.each(function(el, i){
			if (this.options.show === i){
				this.fireEvent('onActive', [this.togglers[i], el]);
			} else {
				for (var fx in this.effects) el.setStyle(fx, 0);
			}
		}, this);
		this.parent(this.elements);
		if ($chk(this.options.display)) this.display(this.options.display);
	},

	/*
	Property: addSection
		Dynamically adds a new section into the accordion at the specified position.

	Arguments:
		toggler - (dom element) the element that toggles the accordion section open.
		element - (dom element) the element that stretches open when the toggler is clicked.
		pos - (integer) the index where these objects are to be inserted within the accordion.
	*/

	addSection: function(toggler, element, pos){
		toggler = $(toggler);
		element = $(element);
		var test = this.togglers.contains(toggler);
		var len = this.togglers.length;
		this.togglers.include(toggler);
		this.elements.include(element);
		if (len && (!test || pos)){
			pos = $pick(pos, len - 1);
			toggler.injectBefore(this.togglers[pos]);
			element.injectAfter(toggler);
		} else if (this.container && !test){
			toggler.inject(this.container);
			element.inject(this.container);
		}
		var idx = this.togglers.indexOf(toggler);
		toggler.addEvent('click', this.display.bind(this, idx));
		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
		element.fullOpacity = 1;
		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
		element.setStyle('overflow', 'hidden');
		if (!test){
			for (var fx in this.effects) element.setStyle(fx, 0);
		}
		return this;
	},

	/*
	Property: display
		Shows a specific section and hides all others. Useful when triggering an accordion from outside.

	Arguments:
		index - integer, the index of the item to show, or the actual element to show.
	*/

	display: function(index){
		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
		this.previous = index;
		var obj = {};
		this.elements.each(function(el, i){
			obj[i] = {};
			var hide = (i != index) || (this.options.alwaysHide && (el.offsetHeight > 0));
			this.fireEvent(hide ? 'onBackground' : 'onActive', [this.togglers[i], el]);
			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
		}, this);
		return this.start(obj);
	},

	showThisHideOpen: function(index){return this.display(index);}

});

Fx.Accordion = Accordion;


var Form = {
	init: function() {},

	error: function(el, message) {
		if (!el) {
			return false;
		}

		var div = el;
		if (el.getTag() != 'div' || !el.hasClass('input')) {
			div = el.getParent('div.input');
		}

		var error = div.getElement('div.error-message');
		if (!error) {
			error = new Element('div', {'class': 'error-message'});
			div.adopt(error);
		}
		error.setText(message).removeClass('hidden');

		div.getElement('label').addClass('error-message');
	},

	removeError: function(el) {
		try {
			var div = el;
			if (el.getTag() != 'div' || !el.hasClass('input')) {
				div = el.getParent('div.input');
			}

			var error = div.getElement('div.error-message');
			if (error) {
				error.addClass('hidden');
			}
			div.getElement('label').removeClass('error-message');
		} catch(e) {}
	},

	clearErrors: function(form) {
		$ES('div.input', form).removeClass('error');
		$ES('div.error-message', form).addClass('hidden');
	},

	validate: function(rules) {
		var invalid = rules.filter(function(args) {
			return !Form.validateField(args.el, args.callback, args.message);
		});

		return (invalid.length == 0) ? true : false;
	},

	validateField: function(el, fn, message) {
		if (!el) {
			return true;
		}

		var result = fn.call(null, el.value);

		if (result == true) {
			Form.removeError(el);
		} else {
			Form.error(el, message);
		}

		return result;
	}
}
Form.init.bind(Form);


var CheckboxLimit = new Class({

	el: null,

	limit: false,

	checked: 0,

	initialize: function(el, limit) {
		this.el = el;
		this.limit = limit;
		this.boxes = el.getElements('input[type=checkbox]');
		this.boxes.addEvent('change', this.onChange.bindWithEvent(this));
		this.boxes.each(function(el) {
			if (el.checked != false) {
				this.checked++;
			}
		}.bind(this));
		this.toggle();
	},

	onChange: function(e) {
		if (e.target.checked) {
			this.checked++;
		} else {
			this.checked--;
		}

		this.toggle();
	},

	toggle: function() {
		if (this.checked >= this.limit) {
			this.disable();
		} else {
			this.enable();
		}
	},

	disable: function() {
		this.boxes.each(function(el) {
			if (el.checked == false) {
				el.setProperty('disabled', 'true');
			}
		});
	},

	enable: function() {
		this.boxes.removeProperty('disabled');
	}
});


var SelectAll = new Class({
	initialize: function() {
		$ES('input.select-all').addEvent('click', this.toggle);
	},

	toggle: function(e) {
		e.target.getParent('table').getElements('tbody input.checkbox').each(function(ch) {
			ch.checked = e.target.checked;
		});
	}
});
window.addEvent('domready', function() {
	new SelectAll();
});


/*
Function: l
	Logs passed variable to Firebug console, if available

Arguments:
	variable - variable to log.
*/
function l(variable) {
	if (window.loadFirebugConsole) {
		loadFirebugConsole();
	}
	if (window.console && window.console.log) {
		window.console.log(variable);
	} else {
		var console = $('console');

		if (!console) {
			console = new Element('div', {'class': 'console'}).injectInside(document.body);
		}

		console.innerHTML += "\n" + variable;
	}
}


function isBody(element) {
	return (/^(?:body|html)$/i).test(element.tagName);
};

