Object.toQueryString = Hash.toQueryString;

/**
 * Observer - Observe formelements for changes
 *
 * - Additional code from clientside.cnet.com
 *
 * @version	1.1
 *
 * @license	MIT-style license
 * @author	Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */
var Observer = new Class({

	Implements: [Options, Events],

	options: {
	periodical: false,
	delay: 1000
	},

	initialize: function(el, onFired, options){
	this.element = $(el) || $$(el);
	this.addEvent('onFired', onFired);
	this.setOptions(options);
	this.bound = this.changed.bind(this);
	this.resume();
	},

	changed: function() {
	var value = this.element.get('value');
	if ($equals(this.value, value)) return;
	this.clear();
	this.value = value;
	this.timeout = this.onFired.delay(this.options.delay, this);
	},

	setValue: function(value) {
	this.value = value;
	this.element.set('value', value);
	return this.clear();
	},

	onFired: function() {
	this.fireEvent('onFired', [this.value, this.element]);
	},

	clear: function() {
	$clear(this.timeout || null);
	return this;
	},

	pause: function(){
	if (this.timer) $clear(this.timer);
	else this.element.removeEvent('keyup', this.bound);
	return this.clear();
	},

	resume: function(){
	this.value = this.element.get('value');
	if (this.options.periodical) this.timer = this.changed.periodical(this.options.periodical, this);
	else this.element.addEvent('keyup', this.bound);
	return this;
	}

});

var $equals = function(obj1, obj2) {
	return (obj1 == obj2 || JSON.encode(obj1) == JSON.encode(obj2));
};

/**
 * Autocompleter
 *
 * http://digitarald.de/project/autocompleter/
 *
 * @version	1.1.2
 *
 * @license	MIT-style license
 * @author	Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

var Autocompleter = new Class({

	Implements: [Options, Events],

	options: {/*
	onOver: $empty,
	onSelect: $empty,
	onSelection: $empty,
	onShow: $empty,
	onHide: $empty,
	onBlur: $empty,
	onFocus: $empty,*/
	minLength: 1,
	markQuery: true,
	width: 'inherit',
	maxChoices: 10,
	injectChoice: null,
	customChoices: null,
	emptyChoices: null,
	visibleChoices: true,
	className: 'autocompleter-choices',
	zIndex: 42,
	delay: 400,
	observerOptions: {},
	fxOptions: {},

	autoSubmit: false,
	overflow: false,
	overflowMargin: 25,
	selectFirst: false,
	filter: null,
	filterCase: false,
	filterSubset: false,
	forceSelect: false,
	selectMode: true,
	choicesMatch: null,

	multiple: false,
	separator: ', ',
	separatorSplit: /\s*[,;]\s*/,
	autoTrim: false,
	allowDupes: false,

	cache: true,
	relative: false
	},

	initialize: function(element, options) {
	this.element = $(element);
	this.setOptions(options);
	this.build();
	this.observer = new Observer(this.element, this.prefetch.bind(this), $merge({
		'delay': this.options.delay
	}, this.options.observerOptions));
	this.queryValue = null;
	if (this.options.filter) this.filter = this.options.filter.bind(this);
	var mode = this.options.selectMode;
	this.typeAhead = (mode == 'type-ahead');
	this.selectMode = (mode === true) ? 'selection' : mode;
	this.cached = [];
	},

	/**
	 * build - Initialize DOM
	 *
	 * Builds the html structure for choices and appends the events to the element.
	 * Override this function to modify the html generation.
	 */
	build: function() {
	if ($(this.options.customChoices)) {
		this.choices = this.options.customChoices;
	} else {
		this.choices = new Element('ul', {
		'class': this.options.className,
		'styles': {
			'zIndex': this.options.zIndex
		}
		}).inject(document.body);
		this.relative = false;
		if (this.options.relative) {
		this.choices.inject(this.element, 'after');
		this.relative = this.element.getOffsetParent();
		}
		this.fix = new OverlayFix(this.choices);
	}
	if (!this.options.separator.test(this.options.separatorSplit)) {
		this.options.separatorSplit = this.options.separator;
	}
	this.fx = (!this.options.fxOptions) ? null : new Fx.Tween(this.choices, $merge({
		'property': 'opacity',
		'link': 'cancel',
		'duration': 200
	}, this.options.fxOptions)).addEvent('onStart', Chain.prototype.clearChain).set(0);
	this.element.setProperty('autocomplete', 'off')
		.addEvent((Browser.Engine.trident || Browser.Engine.webkit) ? 'keydown' : 'keypress', this.onCommand.bind(this))
		.addEvent('click', this.onCommand.bind(this, [false]))
		.addEvent('focus', this.toggleFocus.create({bind: this, arguments: true, delay: 100}))
		.addEvent('blur', this.toggleFocus.create({bind: this, arguments: false, delay: 100}));
	},

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

	toggleFocus: function(state) {
	this.focussed = state;
	if (!state) this.hideChoices(true);
	this.fireEvent((state) ? 'onFocus' : 'onBlur', [this.element]);
	},

	onCommand: function(e) {
	if (!e && this.focussed) return this.prefetch();
	if (e && e.key && !e.shift) {
		switch (e.key) {
		case 'enter':
			if (this.element.value != this.opted) return true;
			if (this.selected && this.visible) {
			this.choiceSelect(this.selected);
			return !!(this.options.autoSubmit);
			}
			break;
		case 'up': case 'down':
			if (!this.prefetch() && this.queryValue !== null) {
			var up = (e.key == 'up');
			this.choiceOver((this.selected || this.choices)[
				(this.selected) ? ((up) ? 'getPrevious' : 'getNext') : ((up) ? 'getLast' : 'getFirst')
			](this.options.choicesMatch), true);
			}
			return false;
		case 'esc': case 'tab':
			this.hideChoices(true);
			break;
		}
	}
	return true;
	},

	setSelection: function(finish) {
	var input = this.selected.inputValue, value = input;
	var start = this.queryValue.length, end = input.length;
	if (input.substr(0, start).toLowerCase() != this.queryValue.toLowerCase()) start = 0;
	if (this.options.multiple) {
		var split = this.options.separatorSplit;
		value = this.element.value;
		start += this.queryIndex;
		end += this.queryIndex;
		var old = value.substr(this.queryIndex).split(split, 1)[0];
		value = value.substr(0, this.queryIndex) + input + value.substr(this.queryIndex + old.length);
		if (finish) {
		var tokens = value.split(this.options.separatorSplit).filter(function(entry) {
			return this.test(entry);
		}, /[^\s,]+/);
		if (!this.options.allowDupes) tokens = [].combine(tokens);
		var sep = this.options.separator;
		value = tokens.join(sep) + sep;
		end = value.length;
		}
	}
	this.observer.setValue(value);
	this.opted = value;
	if (finish || this.selectMode == 'pick') start = end;
	this.element.selectRange(start, end);
	this.fireEvent('onSelection', [this.element, this.selected, value, input]);
	},

	showChoices: function() {
	var match = this.options.choicesMatch, first = this.choices.getFirst(match);
	this.selected = this.selectedValue = null;
	if (this.fix) {
		var pos = this.element.getCoordinates(this.relative), width = this.options.width || 'auto';
		this.choices.setStyles({
		'left': pos.left,
		'top': pos.bottom,
		'width': (width === true || width == 'inherit') ? pos.width : width
		});
	}
	if (!first) return;
	if (!this.visible) {
		this.visible = true;
		this.choices.setStyle('display', '');
		if (this.fx) this.fx.start(1);
		this.fireEvent('onShow', [this.element, this.choices]);
	}
	if (this.options.selectFirst || this.typeAhead || first.inputValue == this.queryValue) this.choiceOver(first, this.typeAhead);
	var items = this.choices.getChildren(match), max = this.options.maxChoices;
	var styles = {'overflowY': 'hidden', 'height': ''};
	this.overflown = false;
	if (items.length > max) {
		var item = items[max - 1];
		styles.overflowY = 'scroll';
		styles.height = item.getCoordinates(this.choices).bottom;
		this.overflown = true;
	};
	this.choices.setStyles(styles);
	this.fix.show();
	if (this.options.visibleChoices) {
		var scroll = document.getScroll(),
		size = document.getSize(),
		coords = this.choices.getCoordinates();
		if (coords.right > scroll.x + size.x) scroll.x = coords.right - size.x;
		if (coords.bottom > scroll.y + size.y) scroll.y = coords.bottom - size.y;
		window.scrollTo(Math.min(scroll.x, coords.left), Math.min(scroll.y, coords.top));
	}
	},

	hideChoices: function(clear) {
	if (clear) {
		var value = this.element.value;
		if (this.options.forceSelect) value = this.opted;
		if (this.options.autoTrim) {
		value = value.split(this.options.separatorSplit).filter($arguments(0)).join(this.options.separator);
		}
		this.observer.setValue(value);
	}
	if (!this.visible) return;
	this.visible = false;
	if (this.selected) this.selected.removeClass('autocompleter-selected');
	this.observer.clear();
	var hide = function(){
		this.choices.setStyle('display', 'none');
		this.fix.hide();
	}.bind(this);
	if (this.fx) this.fx.start(0).chain(hide);
	else hide();
	this.fireEvent('onHide', [this.element, this.choices]);
	},

	prefetch: function() {
	var value = this.element.value, query = value;
	if (this.options.multiple) {
		var split = this.options.separatorSplit;
		var values = value.split(split);
		var index = this.element.getSelectedRange().start;
		var toIndex = value.substr(0, index).split(split);
		var last = toIndex.length - 1;
		index -= toIndex[last].length;
		query = values[last];
	}
	if (query.length < this.options.minLength) {
		this.hideChoices();
	} else {
		if (query === this.queryValue || (this.visible && query == this.selectedValue)) {
		if (this.visible) return false;
		this.showChoices();
		} else {
		this.queryValue = query;
		this.queryIndex = index;
		if (!this.fetchCached()) this.query();
		}
	}
	return true;
	},

	fetchCached: function() {
	return false;
	if (!this.options.cache
		|| !this.cached
		|| !this.cached.length
		|| this.cached.length >= this.options.maxChoices
		|| this.queryValue) return false;
	this.update(this.filter(this.cached));
	return true;
	},

	update: function(tokens) {
	this.choices.empty();
	this.cached = tokens;
	var type = tokens && $type(tokens);
	if (!type || (type == 'array' && !tokens.length) || (type == 'hash' && !tokens.getLength())) {
		(this.options.emptyChoices || this.hideChoices).call(this);
	} else {
		if (this.options.maxChoices < tokens.length && !this.options.overflow) tokens.length = this.options.maxChoices;
		tokens.each(this.options.injectChoice || function(token){
		var choice = new Element('li', {'html': this.markQueryValue(token)});
		choice.inputValue = token;
		this.addChoiceEvents(choice).inject(this.choices);
		}, this);
		this.showChoices();
	}
	},

	choiceOver: function(choice, selection) {
	if (!choice || choice == this.selected) return;
	if (this.selected) this.selected.removeClass('autocompleter-selected');
	this.selected = choice.addClass('autocompleter-selected');
	this.fireEvent('onSelect', [this.element, this.selected, selection]);
	if (!this.selectMode) this.opted = this.element.value;
	if (!selection) return;
	this.selectedValue = this.selected.inputValue;
	if (this.overflown) {
		var coords = this.selected.getCoordinates(this.choices), margin = this.options.overflowMargin,
		top = this.choices.scrollTop, height = this.choices.offsetHeight, bottom = top + height;
		if (coords.top - margin < top && top) this.choices.scrollTop = Math.max(coords.top - margin, 0);
		else if (coords.bottom + margin > bottom) this.choices.scrollTop = Math.min(coords.bottom - height + margin, bottom);
	}
	if (this.selectMode) this.setSelection();
	},

	choiceSelect: function(choice) {
	if (choice) this.choiceOver(choice);
	this.setSelection(true);
	this.queryValue = false;
	this.hideChoices();
	},

	filter: function(tokens) {
	return (tokens || this.tokens).filter(function(token) {
		return this.test(token);
	}, new RegExp(((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp(), (this.options.filterCase) ? '' : 'i'));
	},

	/**
	 * markQueryValue
	 *
	 * Marks the queried word in the given string with <span class="autocompleter-queried">*</span>
	 * Call this i.e. from your custom parseChoices, same for addChoiceEvents
	 *
	 * @param	{String} Text
	 * @return	{String} Text
	 */
	markQueryValue: function(str) {
	return (!this.options.markQuery || !this.queryValue) ? str
		: str.replace(new RegExp('(' + ((this.options.filterSubset) ? '' : '^') + this.queryValue.escapeRegExp() + ')', (this.options.filterCase) ? '' : 'i'), '<span class="autocompleter-queried">$1</span>');
	},

	/**
	 * addChoiceEvents
	 *
	 * Appends the needed event handlers for a choice-entry to the given element.
	 *
	 * @param	{Element} Choice entry
	 * @return	{Element} Choice entry
	 */
	addChoiceEvents: function(el) {
	return el.addEvents({
		'mouseover': this.choiceOver.bind(this, [el]),
		'click': this.choiceSelect.bind(this, [el])
	});
	}
});

var OverlayFix = new Class({

	initialize: function(el) {
	if (Browser.Engine.trident) {
		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();
	}

});

Element.implement({

	getSelectedRange: function() {
	if (!Browser.Engine.trident) return {start: this.selectionStart, end: this.selectionEnd};
	var pos = {start: 0, end: 0};
	var range = this.getDocument().selection.createRange();
	if (!range || range.parentElement() != this) return pos;
	var dup = range.duplicate();
	if (this.type == 'text') {
		pos.start = 0 - dup.moveStart('character', -100000);
		pos.end = pos.start + range.text.length;
	} else {
		var value = this.value;
		var offset = value.length - value.match(/[\n\r]*$/)[0].length;
		dup.moveToElementText(this);
		dup.setEndPoint('StartToEnd', range);
		pos.end = offset - dup.text.length;
		dup.setEndPoint('StartToStart', range);
		pos.start = offset - dup.text.length;
	}
	return pos;
	},

	selectRange: function(start, end) {
	if (Browser.Engine.trident) {
		var diff = this.value.substr(start, end - start).replace(/\r/g, '').length;
		start = this.value.substr(0, start).replace(/\r/g, '').length;
		var range = this.createTextRange();
		range.collapse(true);
		range.moveEnd('character', start + diff);
		range.moveStart('character', start);
		range.select();
	} else {
		this.focus();
		this.setSelectionRange(start, end);
	}
	return this;
	}

});

/* compatibility */

Autocompleter.Base = Autocompleter;

/**
 * Autocompleter.Request
 *
 * http://digitarald.de/project/autocompleter/
 *
 * @version	1.1.2
 *
 * @license	MIT-style license
 * @author	Harald Kirschner <mail [at] digitarald.de>
 * @copyright	Author
 */

Autocompleter.Request = new Class({

	Extends: Autocompleter,

	options: {/*
	indicator: null,
	indicatorClass: null,
	onRequest: $empty,
	onComplete: $empty,*/
	postData: {},
	ajaxOptions: {},
	postVar: 'value'

	},

	query: function(){
	var data = $unlink(this.options.postData) || {};
	data[this.options.postVar] = this.queryValue;
	var indicator = $(this.options.indicator);
	if (indicator) indicator.setStyle('display', '');
	var cls = this.options.indicatorClass;
	if (cls) this.element.addClass(cls);
	this.fireEvent('onRequest', [this.element, this.request, data, this.queryValue]);
	this.request.send({'data': data});
	},

	/**
	 * queryResponse - abstract
	 *
	 * Inherated classes have to extend this function and use this.parent()
	 */
	queryResponse: function() {
	var indicator = $(this.options.indicator);
	if (indicator) indicator.setStyle('display', 'none');
	var cls = this.options.indicatorClass;
	if (cls) this.element.removeClass(cls);
	return this.fireEvent('onComplete', [this.element, this.request]);
	}

});

Autocompleter.Request.JSON = new Class({

	Extends: Autocompleter.Request,

	initialize: function(el, url, options) {
	this.parent(el, options);
	this.request = new Request.JSON($merge({
		'url': url,
		'link': 'cancel'
	}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(response) {
	this.parent();
	this.update(response);
	}

});

Autocompleter.Request.HTML = new Class({

	Extends: Autocompleter.Request,

	initialize: function(el, url, options) {
	this.parent(el, options);
	this.request = new Request.HTML($merge({
		'url': url,
		'link': 'cancel',
		'update': this.choices
	}, this.options.ajaxOptions)).addEvent('onComplete', this.queryResponse.bind(this));
	},

	queryResponse: function(tree, elements) {
	this.parent();
	if (!elements || !elements.length) {
		this.hideChoices();
	} else {
		this.choices.getChildren(this.options.choicesMatch).each(this.options.injectChoice || function(choice) {
		var value = choice.innerHTML;
		choice.inputValue = value;
		this.addChoiceEvents(choice.set('html', this.markQueryValue(value)));
		}, this);
		this.showChoices();
	}

	}

});

/* compatibility */

Autocompleter.Ajax = {
	Base: Autocompleter.Request,
	Json: Autocompleter.Request.JSON,
	Xhtml: Autocompleter.Request.HTML
};


var OverlayFix = new Class({

	initialize: function(el) {
	this.element = $(el);
	if (window.ie){
		this.element.addEvent('trash', this.destroy.bind(this));
		this.fix = new Element('iframe', {
		properties: {
			frameborder: '0',
			scrolling: 'no',
			src: 'javascript:false;'
		},
		styles: {
			position: 'absolute',
			border: 'none',
			display: 'none',
			filter: 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
		}
		}).injectAfter(this.element);
	}
	},

	show: function() {
	if (this.fix) this.fix.setStyles($extend(
		this.element.getCoordinates(), {
		display: '',
		zIndex: (this.element.getStyle('zIndex') || 1) - 1
		}));
	return this;
	},

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

	destroy: function() {
	this.fix.remove();
	}

});

/* Indicators */
var Indicator = new Class({

	options: {
		automatic: true,
		canError: true
	},

	initialize: function(el, options) {
		this.setOptions(options);
		this.element = el;
		if (!this.element) return;
		this.element.indicator = this;
		this.indicator = new Element('div', {
			'class': 'indicator-status',
			html: '&nbsp;'
		}).injectAfter(el);
		this.status = '';
		if (this.options.automatic) {
			this.element.addEvent('change', this.onUpdate.bind(this));
			if (this.element.get('value')) this.setSuccess();
		}
	},
	
	onUpdate: function() {
		if (!this.element.get('value')) {
			if (this.options.canError) this.setError();
		}
		else if (this.status != 'loading') this.setSuccess();
	},

	setEmpty: function() {
		this.status = '';
		this.indicator.removeClass('indicator-status-failure');
		this.indicator.removeClass('indicator-status-success');
		this.indicator.removeClass('indicator-status-loading');
	},

	setLoading: function() {
		this.setEmpty();
		this.status = 'loading';
		this.indicator.addClass('indicator-status-loading');
	},

	setError: function() {
		this.setEmpty();
		this.status = 'error';
		this.indicator.addClass('indicator-status-failure');
	},
	
	setSuccess: function() {
		this.setEmpty();
		this.status = 'success';
		this.indicator.addClass('indicator-status-success');
	}
});
Indicator.implement(new Options);

var currentObject = null;
var SelectDialog = new Class({
	initialize: function(el, link) {
		this.element = el;
		this.link = link
		this.element.set('readonly', true);
		this.element.addClass('readonly');
		this.element.onclick = this.openDialog.bind(this);
		selector = new Element('a', {
			href: 'javascript:void(0);',
			title: 'Select',
			'class': 'icon selectDialogButton',
			html: '&nbsp;',
			events: {
				click: this.openDialog.bind(this)
			}
		}).inject(this.element, 'after');
		
		this.clear_btn = new Element('a', {
			href: 'javascript:void(0);',
			title: 'Clear',
			'class': 'icon clearDialogButton',
			html: '&nbsp;',
			styles: {
				display: (this.element.get('value') ? 'inline' : 'none')
			},
			events: {
				click: function(e) {
					new Event(e).stop();
					this.element.set('value', '');
					this.clear_btn.setStyle('display', 'none');
				}.bind(this)
			}
		}).inject(selector, 'after');
	},
	openDialog: function(e) {
		if (e) new Event(e).stop();
		currentObject = this;
		window.open(this.link, 'selectdialog', 'status=1,toolbar=0,menubar=0,location=0,resizable=1,width=600,height=600,scrollbars=1');
	},
	setValue: function(value) {
		this.element.value = value;
		this.element.realValue = value;
		if (this.element.indicator) this.element.indicator.setSuccess();
		this.clear_btn.setStyle('display', 'inline');
	}
});
function acceptSelection(obj) {
	if (!currentObject) return;
	currentObject.setValue(obj)
	currentObject = null;
}
function returnSelection(obj) {
	window.opener.acceptSelection(obj);
	self.close();
}

var userData = null;
function initializeUserControls() {
	// Initializes the account navigation and notices from an ajax request.
	var controls = $('account');
	if (!controls) return;
	controls.set('html', '<div class="indicator-status indicator-status-loading"></div>');
	var controls = $('accountNotices');
	new Request.JSON({
		url: url('accounts.jsdata') + (controls ? '?notices=1' : ''),
		method: 'get',
		onComplete: function(userData) {
			var controls = $('account');
			controls.empty();
			controls = $(document.createElement('ul')).inject(controls);
			var links = new Array();
			if (userData.cart_items > 0) {
				links.push([url('cart'), 'Cart (' + userData.cart_items + ')'])
			}
			if (userData.is_authenticated) {
				// TODO: move all these urls into the mapper
				if (userData.is_staff) {
					links.push([url('admin'), 'Admin']);
				}
				links.push([url('bizcenter'), 'Business Center']);
				links.push([url('accounts'), 'Your Account']);
				links.push([url('accounts.logout'), 'Logout']);
			} else {
				links.push([url('accounts.login'), 'Login']);
				if (!userData.external_auth) {
					links.push([url('accounts.logout'), 'Register']);
				}
			}
			if (links.length) {
				for (var i=0, link=null; (link=links[i]); i++) {
					var li = new Element('li').set('html', '<a href="' + link[0] + '"><span>' + link[1] + '</span></a>');
					controls.appendChild(li);
				}
			}
			if (userData.messages) {
				var notices = $('accountNotices');
				for (var i=0, notice=null; (notice = userData.messages[i]); i++) {
					new Element('p', {
						'html': notice[1],
						'class': 'notice notice_' + notice[0], 'id': 'notice_' + i
					}).inject(notices);
				}
			}
		}
	}).send();
}

var Rating = new Class({
	initialize: function(element, content_type_id, object_id, field_name) {
		this.element = element;
		this.content_type_id = content_type_id;
		this.object_id = object_id
		this.field_name = field_name;
		element.getElements('li').each(function(el){
			if (el.get('rel')) {
				el.addEvent('click', this.handleClick.bind(this));
			}
		}.bind(this));
		this.status = element.getElement('li.indicator');
	},
	setRating: function(score) {
		this.element.getElements('li').each(function(el){
			if (el.get('rel') == score) {
				el.addClass('btn-active');
			} else {
				el.removeClass('btn-active');
			}
		}.bind(this));
	},
	handleClick: function(event) {
		this.status.removeClass('indicator-success');
		this.status.addClass('indicator-loading');
		new Event(event).stop();
		var target = (event.target.tagName == 'A' ? event.target.parentNode : event.target);
		new Request.JSON({
			url: API_URL + 'ratings.json',
			method: 'post',
			onSuccess: function(data) {
				if (data.code == 0) {
					this.setRating(data.response.score);
				} else {
					alert(data.response.message);
				}
				this.status.addClass('indicator-success');
			}.bind(this),
			onComplete: function() {
				this.status.removeClass('indicator-loading');
			}.bind(this),
			data: {
				method: 'add',
				content_type_id: this.content_type_id,
				object_id: this.object_id,
				field_name: this.field_name,
				score: target.get('rel')
			}
		}).send();	}
});
Autocompleter.Request.API = {};
Autocompleter.Request.API.Cities = new Class({
	Extends: Autocompleter.Request.JSON,
	queryResponse: function(data) {
		this.parent();
		this.update(data.response.cities);
	}
});
Autocompleter.Request.API.Tags = new Class({
	Extends: Autocompleter.Request.JSON,
	queryResponse: function(data) {
		this.parent();
		this.update(data.response.tags);
	}
});

var StateControl = new Class({
	initialize: function(element, country_el, country_initial) {
		this.element = element;
		this.indicator = new Indicator(this.element.getElement('select'));
		this.country_el = country_el;
		this.country_val = country_initial
		if (country_el) {
			this.country_el.addEvent('change', this.onCountryChange.bind(this));
			this.setCountry(country_el.get('value'));
		}
		else {
			this.setCountry(country_initial);
		}
	},
	onCountryChange: function(event) {
		var value = event.target.get('value');
		this.setCountry(value);
	},
	setCountry: function(value) {
		if (value == 'uk') {
			this.element.getElement('label').set('text', 'County:');
		}
		else {
			this.element.getElement('label').set('text', 'State:');
		}
		
		if (value == this.country_val) return;

		this.country_val = value;

		this.indicator.setLoading();
		var sel = this.element.getElement('select').setProperty('disabled', true)
		sel.addClass('disabled').empty();
		new Element('option').set({
			'html': 'Loading, please wait...',
			'value': ''
		}).inject(sel);

		new Request.JSON({
			url: API_URL + 'states.json',
			method: 'post',
			data: {
				'method': 'list',
				'country': value,
			},
			onSuccess: function(data) {
				var sel = this.element.getElement('select').empty();
				new Element('option', {
					'value': '',
					'html': '---------'
				}).inject(sel);
				for (var i=0, state=null; (state = data.response.states[i]); i++) {
					new Element('option', {
						'value': state.id,
						'html': state.name
					}).inject(sel);
				}
				sel.set('disabled', false).removeClass('disabled');
				this.indicator.setEmpty();
			}.bind(this),
			onFailure: function() {
				var sel = this.element.getElement('select').empty();
				new Element('option').set({
					'html': 'Loading, please wait...',
					'value': ''
				}).inject(sel);
				sel.set('disabled', false).removeClass('disabled');
				this.indicator.setError();
			}.bind(this)
		}).send();
	}
});
var TagControl = new Class({
	initialize: function(element, prefix) {
		if (prefix === undefined) var prefix = null;
		this.element = element;
		this.prefix = prefix;
		this.autocompleter = new Autocompleter.Request.API.Tags(this.element.getElement('input'), API_URL + 'tags.json', {
			postVar: 'query',
			multiple: true,
			selectFirst: true,
			ajaxOptions: {
				method: 'post'
			},
			postData: {
				'method': 'search',
				'prefix': this.prefix,
			},
			minLength: 2,
			injectChoice: function(choice) {
				var el = new Element('li', {
					'html': this.markQueryValue(choice.name)
				});
				el.inputValue = choice.name;
				el.inputData = choice;
				this.addChoiceEvents(el).inject(this.choices);
			},
		});
	}
})
var CityControl = new Class({
	initialize: function(element, country_el, state_el, country_initial, state_initial) {
		this.element = element;
		this.indicator = new Indicator(this.element.getElement('input'));
		this.country_el = country_el;
		this.state_el = state_el;
		this.state_val = this.state_el.get('value') || state_initial;
		this.country_val = this.country_el.get('value') || country_initial;
		this.autocomplete = new Autocompleter.Request.API.Cities(this.element.getElement('input'), API_URL + 'cities.json', {
			postVar: 'query',
			forceSelect: true,
			selectFirst: true,
			ajaxOptions: {
				method: 'post'
			},
			postData: {
				'method': 'search',
				'country': this.country_val,
				'state': this.state_val,
			},
			minLength: 2,
			onRequest: function(el) {
				this.indicator.setLoading();
			}.bind(this),
			onComplete: function(el) {
				this.indicator.setEmpty();
			}.bind(this),
			onFailure: function(el) {
				this.indicator.setError();
			}.bind(this),
			onSelect: function(el, value) {
				if (value) {
					this.indicator.setSuccess();
				}
				else {
					this.indicator.setError();
				}
			}.bind(this),
			injectChoice: function(choice) {
				var el = new Element('li', {
					'html': this.markQueryValue(choice.name)
				});
				el.inputValue = choice.name;
				el.inputData = choice;
				this.addChoiceEvents(el).inject(this.choices);
			},
		});
		if (this.country_el) {
			this.country_el.addEvent('change', this.onCountryChange.bind(this));
		}
		else {
			this.setCountry(this.country_val);
		}
		if (this.state_el) {
			this.state_el.addEvent('change', this.onStateChange.bind(this));
			this.setState(this.state_el.get('value'));
		}
		else {
			this.setState(this.state_val);
		}
	},
	onCountryChange: function(event) {
		var value = event.target.get('value');
		this.setCountry(value);
	},
	onStateChange: function(event) {
		var value = event.target.get('value');
		this.setState(value);
	},
	setState: function(value) {
		this.state_val = value;

		this.autocomplete.setOptions({
			postData: {
				state: this.state_val,
				country: this.country_val,
				method: 'search'
			}
		});
	},
	setCountry: function(value) {
		this.country_val = value;
		if (value == 'uk') {
			this.element.getElement('label').set('text', 'Town:');
		} else {
			this.element.getElement('label').set('text', 'City:');
		}

		this.autocomplete.setOptions({
			postData: {
				state: this.state_val,
				country: this.country_val,
				method: 'search'
			}
		});
	}
})
var ZipControl = new Class({
	initialize: function(element, country_el, country_initial) {
		this.element = element;
		this.country_val = country_initial;
		this.country_el = country_el;
		if (this.country_el) {
			this.country_el.addEvent('change', this.onCountryChange.bind(this));
			this.setCountry(this.country_el.get('value'));
		}
		else {
			this.setCountry(this.country_val);
		}
	},
	onCountryChange: function(event) {
		var value = event.target.get('value');
		this.setCountry(value);
	},
	setCountry: function(value) {
		this.country_val = value;
		if (value == 'uk' || value == 'ca') {
			this.element.getElement('label').set('text', 'Postal Code:');
		} else if (value == 'us') {
			this.element.getElement('label').set('text', 'Zip Code:');
		} else {
			this.element.getElement('label').set('text', 'Postal / Zip Code:');
		}
	}
});
var HoursControl = new Class({
	initialize: function(element) {
		this.element = $(element);
		this.isActive = false;
		this.isSplit = false;
		this.is24 = false;
		this.hourBlocks = this.element.getElements('.hours_block');
		this.firstChild = this.hourBlocks[0];
		this.toggles = this.element.getElements('input[name=hours]').each(function(el){
			el.addEvent('change', function(e){
				this.toggleHours(parseInt($(e.target).get('value')));
			}.bind(this));
			if (el.get('checked')) {
				this.toggleHours(parseInt(el.get('value')));
			}
		}.bind(this));
		this.splitHours = this.element.getElement('input[name=split_hours]').addEvent('change', function(e){
			this.toggleSplit($(e.target).get('checked'));
		}.bind(this));
		this.toggleSplit(this.splitHours.get('checked'));
		new Element('a', {
			'text': 'Apply to all',
			'class': 'hours-apply-all',
			'href': 'javascript:void(0)',
			'events': {
				'click': function(e) {
					var base = {
						from: this.firstChild.getElement('.hours_from').get('value'),
						to: this.firstChild.getElement('.hours_to').get('value'),
						from2: this.firstChild.getElement('.hours_from2').get('value'),
						to2: this.firstChild.getElement('.hours_to2').get('value')
					};
					this.hourBlocks.each(function(el){
						el.getElement('.hours_from').set('value', base.from);
						el.getElement('.hours_to').set('value', base.to);
						el.getElement('.hours_to2').set('value', base.to2);
						el.getElement('.hours_from2').set('value', base.from2);
					}.bind(this));
				}.bind(this)
			}
		}).inject(this.firstChild.getElement('.hours_allday').parentNode, 'after');
		this.element.getElements('.hours_block').each(function(el){
			var box = el.getElement('.hours_closed').addEvent('change', function(e){
				// is there a better way to set this up and keep scope?
				var el = e.target.parentNode.parentNode.parentNode;
				this.toggleClosed(el, e.target.checked);
			}.bind(this));
			this.toggleClosed(el, box.get('checked'));
		}.bind(this));
	},
	toggleClosed: function(el, show_or_hide) {
		if (show_or_hide) {
			el.getElement('.hours_container').setStyle('visibility', 'hidden');
			el.getElement('.hours_allday').addClass('disabled')
			el.getElement('.hours_allday').set('disabled', true)
		}
		else {
			el.getElement('.hours_container').setStyle('visibility', 'visible');
			el.getElement('.hours_allday').removeClass('disabled')
			el.getElement('.hours_allday').set('disabled', false)
		}
		if (this.isSplit) {
			el.getElement('.split_hours_container').setStyle('display', (show_or_hide ? 'none' : 'block'));
		}
	},
	toggleHours: function(show_or_hide) {
		this.isActive = show_or_hide;
		var els = this.element.getElements('.hours select');
		els.set('disabled', !show_or_hide);
		if (show_or_hide) els.removeClass('disabled');
		else els.addClass('disabled');

		var els = this.element.getElements('.hours input');
		els.set('disabled', !show_or_hide);
		if (show_or_hide) els.removeClass('disabled');
		else els.addClass('disabled');
	},
	toggleSplit: function(show_or_hide) {
		this.isSplit = show_or_hide;
		if (this.isSplit) {
			this.element.getElements('.split_hours_container').each(function(el){
				el.setStyle('display', ((this.isSplit && !el.parentNode.getElement('.hours_closed').get('checked')) ? 'block' : 'none'));
			}.bind(this));
		}
		else {
			this.element.getElements('.split_hours_container').setStyle('display', (this.isSplit ? 'block' : 'none'));
		}
	}
});

var LinkMenu = new Class({
	initialize: function(element, pairs) {
		this.element = $(element);
		this.isOpen = false;
		this.element.setStyles({
			'position': 'relative',
			'padding-right': '30px'
		});
		this.container = new Element('div', {
			'class': 'linkmenu'
		}).inject(this.element, 'inside');
		new Element('a', {
			'href': 'javascript:void(0)',
			'text': 'Actions',
			'class': 'linkmenu-toggle',
			'events': {
				'click': function(e){
					if (this.isOpen) {
						this.wrapper.setStyle('display', 'none');
					}
					else {
						this.wrapper.setStyle('display', 'block')
					}
					this.isOpen = !this.isOpen;
				}.bind(this)
			}
		}).inject(this.container);
		this.wrapper = new Element('ul', {
			'class': 'linkmenu-links',
			'styles': {
				'display': 'none'
			}
		}).inject(this.container);
		for (var i=0, pair=null; (pair=pairs[i]); i++) {
			this.addLink(pair[0], pair[1]);
		}
	},
	addLink: function(label, href) {
		new Element('a', {
			'href': href,
			'text': label
		}).inject(this.wrapper.appendChild(new Element('li')));
	}
});

var PassiveInputLabel = new Class({
	initialize: function(element, label) {
		this.element = $(element);
		this.label = label;
		this.element.addEvents({
			'blur': this.test.bind(this),
			'focus': function(e){
				if (this.element.get('value') == this.label)
				{
					this.element.set('value', '');
					this.element.removeClass('inactive');
				}
			}.bind(this),
		});
		this.test();
	},
	test: function(){
		if (!this.element.get('value') || this.element.get('value') == this.label) {
			this.element.set('value', this.label);
			this.element.addClass('inactive');
		}
		else {
			this.element.removeClass('inactive');
		}
	}
});

var URLMapper = new Class({
	initialize: function(mapping) {
		this.mapping = mapping;
	},
	url: function(name) {
		return this.mapping[name];
	}
});