if(typeof(Control) == 'undefined')
	Control = {};
	
var $proc = function(proc){
	return typeof(proc) == 'function' ? proc : function(){return proc};
};

var $value = function(value){
	return typeof(value) == 'function' ? value() : value;
};

Object.Event = {
	extend: function(object){
		object._objectEventSetup = function(event_name){
			this._observers = this._observers || {};
			this._observers[event_name] = this._observers[event_name] || [];
		};
		object.observe = function(event_name,observer){
			if(typeof(event_name) == 'string' && typeof(observer) != 'undefined'){
				this._objectEventSetup(event_name);
				if(!this._observers[event_name].include(observer))
					this._observers[event_name].push(observer);
			}else
				for(var e in event_name)
					this.observe(e,event_name[e]);
		};
		object.stopObserving = function(event_name,observer){
			this._objectEventSetup(event_name);
			if(event_name && observer)
				this._observers[event_name] = this._observers[event_name].without(observer);
			else if(event_name)
				this._observers[event_name] = [];
			else
				this._observers = {};
		};
		object.observeOnce = function(event_name,outer_observer){
			var inner_observer = function(){
				outer_observer.apply(this,arguments);
				this.stopObserving(event_name,inner_observer);
			}.bind(this);
			this._objectEventSetup(event_name);
			this._observers[event_name].push(inner_observer);
		};
		object.notify = function(event_name){
			this._objectEventSetup(event_name);
			var collected_return_values = [];
			var args = $A(arguments).slice(1);
			try{
				for(var i = 0; i < this._observers[event_name].length; ++i)
					collected_return_values.push(this._observers[event_name][i].apply(this._observers[event_name][i],args) || null);
			}catch(e){
				if(e == $break)
					return false;
				else
					throw e;
			}
			return collected_return_values;
		};
		if(object.prototype){
			object.prototype._objectEventSetup = object._objectEventSetup;
			object.prototype.observe = object.observe;
			object.prototype.stopObserving = object.stopObserving;
			object.prototype.observeOnce = object.observeOnce;
			object.prototype.notify = function(event_name){
				if(object.notify){
					var args = $A(arguments).slice(1);
					args.unshift(this);
					args.unshift(event_name);
					object.notify.apply(object,args);
				}
				this._objectEventSetup(event_name);
				var args = $A(arguments).slice(1);
				var collected_return_values = [];
				try{
					if(this.options && this.options[event_name] && typeof(this.options[event_name]) == 'function')
						collected_return_values.push(this.options[event_name].apply(this,args) || null);
					for(var i = 0; i < this._observers[event_name].length; ++i)
						collected_return_values.push(this._observers[event_name][i].apply(this._observers[event_name][i],args) || null);
				}catch(e){
					if(e == $break)
						return false;
					else
						throw e;
				}
				return collected_return_values;
			};
		}
	}
};

/* Begin Core Extensions */

//Element.observeOnce
Element.addMethods({
	observeOnce: function(element,event_name,outer_callback){
		var inner_callback = function(){
			outer_callback.apply(this,arguments);
			Element.stopObserving(element,event_name,inner_callback);
		};
		Element.observe(element,event_name,inner_callback);
	}
});

//mouseenter, mouseleave
//from http://dev.rubyonrails.org/attachment/ticket/8354/event_mouseenter_106rc1.patch
Object.extend(Event, (function() {
	var cache = Event.cache;

	function getEventID(element) {
		if (element._prototypeEventID) return element._prototypeEventID[0];
		arguments.callee.id = arguments.callee.id || 1;
		return element._prototypeEventID = [++arguments.callee.id];
	}

	function getDOMEventName(eventName) {
		if (eventName && eventName.include(':')) return "dataavailable";
		//begin extension
		if(!Prototype.Browser.IE){
			eventName = {
				mouseenter: 'mouseover',
				mouseleave: 'mouseout'
			}[eventName] || eventName;
		}
		//end extension
		return eventName;
	}

	function getCacheForID(id) {
		return cache[id] = cache[id] || { };
	}

	function getWrappersForEventName(id, eventName) {
		var c = getCacheForID(id);
		return c[eventName] = c[eventName] || [];
	}

	function createWrapper(element, eventName, handler) {
		var id = getEventID(element);
		var c = getWrappersForEventName(id, eventName);
		if (c.pluck("handler").include(handler)) return false;

		var wrapper = function(event) {
			if (!Event || !Event.extend ||
				(event.eventName && event.eventName != eventName))
					return false;

			Event.extend(event);
			handler.call(element, event);
		};
		
		//begin extension
		if(!(Prototype.Browser.IE) && ['mouseenter','mouseleave'].include(eventName)){
			wrapper = wrapper.wrap(function(proceed,event) {	
				var rel = event.relatedTarget;
				var cur = event.currentTarget;			 
				if(rel && rel.nodeType == Node.TEXT_NODE)
					rel = rel.parentNode;	  
				if(rel && rel != cur && !rel.descendantOf(cur))	  
					return proceed(event);   
			});	 
		}
		//end extension

		wrapper.handler = handler;
		c.push(wrapper);
		return wrapper;
	}

	function findWrapper(id, eventName, handler) {
		var c = getWrappersForEventName(id, eventName);
		return c.find(function(wrapper) { return wrapper.handler == handler });
	}

	function destroyWrapper(id, eventName, handler) {
		var c = getCacheForID(id);
		if (!c[eventName]) return false;
		c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
	}

	function destroyCache() {
		for (var id in cache)
			for (var eventName in cache[id])
				cache[id][eventName] = null;
	}

	if (window.attachEvent) {
		window.attachEvent("onunload", destroyCache);
	}

	return {
		observe: function(element, eventName, handler) {
			element = $(element);
			var name = getDOMEventName(eventName);

			var wrapper = createWrapper(element, eventName, handler);
			if (!wrapper) return element;

			if (element.addEventListener) {
				element.addEventListener(name, wrapper, false);
			} else {
				element.attachEvent("on" + name, wrapper);
			}

			return element;
		},

		stopObserving: function(element, eventName, handler) {
			element = $(element);
			var id = getEventID(element), name = getDOMEventName(eventName);

			if (!handler && eventName) {
				getWrappersForEventName(id, eventName).each(function(wrapper) {
					element.stopObserving(eventName, wrapper.handler);
				});
				return element;

			} else if (!eventName) {
				Object.keys(getCacheForID(id)).each(function(eventName) {
					element.stopObserving(eventName);
				});
				return element;
			}

			var wrapper = findWrapper(id, eventName, handler);
			if (!wrapper) return element;

			if (element.removeEventListener) {
				element.removeEventListener(name, wrapper, false);
			} else {
				element.detachEvent("on" + name, wrapper);
			}

			destroyWrapper(id, eventName, handler);

			return element;
		},

		fire: function(element, eventName, memo) {
			element = $(element);
			if (element == document && document.createEvent && !element.dispatchEvent)
				element = document.documentElement;

			var event;
			if (document.createEvent) {
				event = document.createEvent("HTMLEvents");
				event.initEvent("dataavailable", true, true);
			} else {
				event = document.createEventObject();
				event.eventType = "ondataavailable";
			}

			event.eventName = eventName;
			event.memo = memo || { };

			if (document.createEvent) {
				element.dispatchEvent(event);
			} else {
				element.fireEvent(event.eventType, event);
			}

			return Event.extend(event);
		}
	};
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
	fire:			Event.fire,
	observe:		Event.observe,
	stopObserving:	Event.stopObserving
});

Object.extend(document, {
	fire:			Element.Methods.fire.methodize(),
	observe:		Element.Methods.observe.methodize(),
	stopObserving:	Element.Methods.stopObserving.methodize()
});

//mouse:wheel
(function(){
	function wheel(event){
		var delta;
		// normalize the delta
		if(event.wheelDelta) // IE & Opera
			delta = event.wheelDelta / 120;
		else if (event.detail) // W3C
			delta =- event.detail / 3;
		if(!delta)
			return;
		var custom_event = event.element().fire('mouse:wheel',{
			delta: delta
		});
		if(custom_event.stopped){
			event.stop();
			return false;
		}
	}
	document.observe('mousewheel',wheel);
	document.observe('DOMMouseScroll',wheel);
})();

/* End Core Extensions */

//from PrototypeUI
var IframeShim = Class.create({
	initialize: function() {
		this.element = new Element('iframe',{
			style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none',
			src: 'javascript:void(0);',
			frameborder: 0 
		});
		$(document.body).insert(this.element);
	},
	hide: function() {
		this.element.hide();
		return this;
	},
	show: function() {
		this.element.show();
		return this;
	},
	positionUnder: function(element) {
		var element = $(element);
		var offset = element.cumulativeOffset();
		var dimensions = element.getDimensions();
		this.element.setStyle({
			left: offset[0] + 'px',
			top: offset[1] + 'px',
			width: dimensions.width + 'px',
			height: dimensions.height + 'px',
			zIndex: element.getStyle('zIndex') - 1
		}).show();
		return this;
	},
	setBounds: function(bounds) {
		for(prop in bounds)
			bounds[prop] += 'px';
		this.element.setStyle(bounds);
		return this;
	},
	destroy: function() {
		if(this.element)
			this.element.remove();
		return this;
	}
});

if(typeof(Prototype) == "undefined")
	throw "Cookie requires Prototype to be loaded."
if(typeof(Object.Event) == "undefined")
	throw "Cookie requires Object.Event to be loaded.";

var Cookie = {
	set: function(name,value,seconds){
		if(seconds){
			var d = new Date();
			d.setTime(d.getTime() + (seconds * 1000));
			var expiry = '; expires=' + d.toGMTString();
		}else
			var expiry = '';
		Cookie.notify('set',name,value);
		document.cookie = name + "=" + value + expiry + "; path=/";
	},
	get: function(name){
		Cookie.notify('get',name);
		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for(var i = 0; i < ca.length; i++){
			var c = ca[i];
			while(c.charAt(0) == ' ')
				c = c.substring(1,c.length);
			if(c.indexOf(nameEQ) == 0)
				return c.substring(nameEQ.length,c.length);
		}
		return null;
	},
	unset: function(name){
		Cookie.notify('unset',name);
		Cookie.set(name,'',-1);
	}
};
Object.Event.extend(Cookie);

if(typeof(Prototype) == "undefined")
	throw "Event.Behavior requires Prototype to be loaded.";
if(typeof(Object.Event) == "undefined")
	throw "Event.Behavior requires Object.Event to be loaded.";
	
Event.Behavior = {
	addVerbs: function(verbs){
		for(name in verbs){
			v = new Event.Behavior.Verb(verbs[name]);
			Event.Behavior.Verbs[name] = v;
			Event.Behavior[name.underscore()] = Event.Behavior[name] = v.getCallbackForStack.bind(v);
		}
	},
	addEvents: function(events){
		$H(events).each(function(event_type){
			Event.Behavior.Adjective.prototype[event_type.key.underscore()] = Event.Behavior.Adjective.prototype[event_type.key] = function(){
				this.nextConditionType = 'and';
				this.events.push(event_type.value);
				this.attachObserver(false);
				return this;
			};
		});
	},
	invokeElementMethod: function(element,action,args){
		if(typeof(element) == 'function'){
			return $A(element()).each(function(e){
				if(typeof(args[0]) == 'function'){
					return $A(args[0]).each(function(a){
						return $(e)[action].apply($(e),(a ? [a] : []));
					});
				}else
					return $(e)[action].apply($(e),args || []);
			});
		}else
			return $(element)[action].apply($(element),args || []);
	}
};

Event.Behavior.Verbs = $H({});

Event.Behavior.Verb = Class.create();
Object.extend(Event.Behavior.Verb.prototype,{
	originalAction: false,
	execute: false,
	executeOpposite: false,
	target: false,
	initialize: function(action){
		this.originalAction = action;
		this.execute = function(action,target,argument){
			return (argument)
				? action(target,argument)
				: action(target)
			;
		}.bind(this,action);
	},
	setOpposite: function(opposite_verb){
		opposite_action = opposite_verb.originalAction;
		this.executeOpposite = function(opposite_action,target,argument){
			return (argument)
				? opposite_action(target,argument)
				: opposite_action(target)
			;
		}.bind(this,opposite_action);
	},
	getCallbackForStack: function(argument){
		return new Event.Behavior.Noun(this,argument);
	}
});

Event.Behavior.addVerbs({
	call: function(callback){
		callback();
	},
	show: function(element){
		return Event.Behavior.invokeElementMethod(element,'show');
	},
	hide: function(element){
		return Event.Behavior.invokeElementMethod(element,'hide');
	},
	remove: function(element){
		return Event.Behavior.invokeElementMethod(element,'remove');
	},
	setStyle: function(element,styles){
		return Event.Behavior.invokeElementMethod(element,'setStyle',[(typeof(styles) == 'function' ? styles() : styles)]);
	},
	addClassName: function(element,class_name){
		return Event.Behavior.invokeElementMethod(element,'addClassName',[(typeof(class_name) == 'function' ? class_name() : class_name)]);
	},
	removeClassName: function(element,class_name){
		return Event.Behavior.invokeElementMethod(element,'removeClassName',[(typeof(class_name) == 'function' ? class_name() : class_name)]);
	},
	setClassName: function(element,class_name){
		c = (typeof(class_name) == 'function') ? class_name() : class_name;
		if(typeof(element) == 'function'){
			return $A(element()).each(function(e){
				$(e).className = c;
			});
		}else
			return $(element).className = c;
	},
	update: function(content,element){
		return Event.Behavior.invokeElementMethod(element,'update',[(typeof(content) == 'function' ? content() : content)]);
	},
	replace: function(content,element){
		return Event.Behavior.invokeElementMethod(element,'replace',[(typeof(content) == 'function' ? content() : content)]);
	}
});
Event.Behavior.Verbs.show.setOpposite(Event.Behavior.Verbs.hide);
Event.Behavior.Verbs.hide.setOpposite(Event.Behavior.Verbs.show);
Event.Behavior.Verbs.addClassName.setOpposite(Event.Behavior.Verbs.removeClassName);
Event.Behavior.Verbs.removeClassName.setOpposite(Event.Behavior.Verbs.addClassName);

Event.Behavior.Noun = Class.create();
Object.extend(Event.Behavior.Noun.prototype,{
	verbs: false,
	verb: false,
	argument: false,
	subject: false,
	target: false,
	initialize: function(verb,argument){
		//this.verbs = $A([]);
		this.verb = verb;
		this.argument = argument;
	},
	execute: function(){
		return (this.target)
			? this.verb.execute(this.target,this.argument)
			: this.verb.execute(this.argument)
		;
	},
	executeOpposite: function(){
		return (this.target)
			? this.verb.executeOpposite(this.target,this.argument)
			: this.verb.executeOpposite(this.argument)
		;
	},
	when: function(subject){
		this.subject = subject;
		return new Event.Behavior.Adjective(this);
	},
	getValue: function(){
		return Try.these(
			function(){return $(this.subject).getValue();}.bind(this),
			function(){return $(this.subject).options[$(this.subject).options.selectedIndex].value;}.bind(this),
			function(){return $(this.subject).value;}.bind(this),
			function(){return $(this.subject).innerHTML;}.bind(this)
		);
	},
	containsValue: function(match){
		value = this.getValue();
		if(typeof(match) == 'function'){
			return $A(match()).include(value);
		}else
			return value.match(match);
	},
	setTarget: function(target){
		this.target = target;
		return this;
	},
	and: function(){

	}
});
Event.Behavior.Noun.prototype._with = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.on = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.of = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.to = Event.Behavior.Noun.prototype.setTarget;
Event.Behavior.Noun.prototype.from = Event.Behavior.Noun.prototype.setTarget;

Event.Behavior.Adjective = Class.create();
Object.extend(Event.Behavior.Adjective.prototype,{
	noun: false,
	lastConditionName: '',
	nextConditionType: 'and',
	conditions: $A([]),
	events: $A([]),
	attached: false,
	initialize: function(noun){
		this.conditions = $A([]);
		this.events = $A([]);
		this.noun = noun;
	},
	attachObserver: function(execute_on_load){
		if(this.attached){
			//this may call things multiple times, but is the only way to gaurentee correct state on startup
			if(execute_on_load)
				this.execute();
			return;
		}
		this.attached = true;
		if(typeof(this.noun.subject) == 'function'){
			$A(this.noun.subject()).each(function(subject){
				(this.events.length > 0 ? this.events : $A(['change'])).each(function(event_name){
					(subject.observe ? subject : $(subject)).observe(event_name,function(){
						this.execute();
					}.bind(this));
				}.bind(this));
			}.bind(this));
		}else{
			(this.events.length > 0 ? this.events : $A(['change'])).each(function(event_name){
				$(this.noun.subject).observe(event_name,function(){
					this.execute();
				}.bind(this));
			}.bind(this));
		}
		if(execute_on_load)
			this.execute();
	},
	execute: function(){
		if(this.match())
			return this.noun.execute();
		else if(this.noun.verb.executeOpposite)
			this.noun.executeOpposite();
	},
	attachCondition: function(callback){
		this.conditions.push([this.nextConditionType,callback.bind(this)]);
	},
	match: function(){
		if(this.conditions.length == 0)
			return true;
		else{
			return this.conditions.inject(new Boolean(),function(bool,condition){
				return (condition[0] == 'and') ? (bool && condition[1]()) : (bool || condition[1]());
			});
		}
	},
	//conditions
	is: function(item){
		this.lastConditionName = 'is';
		this.attachCondition(function(item){
			return (typeof(item) == 'function' ? item() : item) == this.noun.getValue();
		}.bind(this,item));
		this.attachObserver(true);
		return this;
	},
	isNot: function(item){
		this.lastConditionName = 'isNot';
		this.attachCondition(function(item){
			return (typeof(item) == 'function' ? item() : item) != this.noun.getValue();
		}.bind(this,item));
		this.attachObserver(true);
		return this;
	},
	contains: function(item){
		this.lastConditionName = 'contains';
		this.attachCondition(function(item){
			return this.noun.containsValue(item);
		}.bind(this,item));
		this.attachObserver(true);
		return this;
	},
	within: function(item){
		this.lastConditionName = 'within';
		this.attachCondition(function(item){
			
		}.bind(this,item));
		this.attachObserver(true);
		return this;
	},
	//events
	change: function(){
		this.nextConditionType = 'and';
		this.attachObserver(true);
		return this;
	},
	and: function(condition){
		this.attached = false;
		this.nextConditionType = 'and';
		if(condition)
			this[this.lastConditionName](condition);
		return this;
	},
	or: function(condition){
		this.attached = false;
		this.nextConditionType = 'or';
		if(condition)
			this[this.lastConditionName](condition);
		return this;
	}
});

Event.Behavior.addEvents({
	losesFocus: 'blur',
	gainsFocus: 'focus',
	isClicked: 'click',
	isDoubleClicked: 'dblclick',
	keyPressed: 'keypress'
});

Event.Behavior.Adjective.prototype.is_not = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.include = Event.Behavior.Adjective.prototype.contains;
Event.Behavior.Adjective.prototype.includes = Event.Behavior.Adjective.prototype.contains;
Event.Behavior.Adjective.prototype.are = Event.Behavior.Adjective.prototype.is;
Event.Behavior.Adjective.prototype.areNot = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.are_not = Event.Behavior.Adjective.prototype.isNot;
Event.Behavior.Adjective.prototype.changes = Event.Behavior.Adjective.prototype.change;

if(typeof(Prototype) == "undefined")
	throw "Control.Rating requires Prototype to be loaded.";
if(typeof(Object.Event) == "undefined")
	throw "Control.Rating requires Object.Event to be loaded.";

Control.Rating = Class.create({
	initialize: function(container,options){
		Control.Rating.instances.push(this);
		this.value = false;
		this.links = [];
		this.container = $(container);
		this.container.update('');
		this.options = {
			min: 1,
			max: 5,
			rated: false,
			input: false,
			reverse: false,
			capture: true,
			multiple: false,
			classNames: {
				off: 'rating_off',
				half: 'rating_half',
				on: 'rating_on',
				selected: 'rating_selected'
			},
			updateUrl: false,
			updateParameterName: 'value',
			afterChange: Prototype.emptyFunction
		};
		Object.extend(this.options,options || {});
		if(this.options.value){
			this.value = this.options.value;
			delete this.options.value;
		}
		if(this.options.input){
			this.options.input = $(this.options.input);
			this.options.input.observe('change',function(input){
				this.setValueFromInput(input);
			}.bind(this,this.options.input));
			this.setValueFromInput(this.options.input,true);
		}
		var range = $R(this.options.min,this.options.max);
		(this.options.reverse ? $A(range).reverse() : range).each(function(i){
			var link = this.buildLink(i);
			this.container.appendChild(link);
			this.links.push(link);
		}.bind(this));
		this.setValue(this.value || this.options.min - 1,false,true);
	},
	buildLink: function(rating){
		var link = $(document.createElement('a'));
		link.value = rating;
		if(this.options.multiple || (!this.options.rated && !this.options.multiple)){
			link.href = '';
			link.onmouseover = this.mouseOver.bind(this,link);
			link.onmouseout = this.mouseOut.bind(this,link);
			link.onclick = this.click.bindAsEventListener(this,link);
		}else{
			link.style.cursor = 'default';
			link.observe('click',function(event){
				Event.stop(event);
				return false;
			}.bindAsEventListener(this));
		}
		link.addClassName(this.options.classNames.off);
		return link;
	},
	disable: function(){
		this.links.each(function(link){
			link.onmouseover = Prototype.emptyFunction;
			link.onmouseout = Prototype.emptyFunction;
			link.onclick = Prototype.emptyFunction;
			link.observe('click',function(event){
				Event.stop(event);
				return false;
			}.bindAsEventListener(this));
			link.style.cursor = 'default';
		}.bind(this));
	},
	setValueFromInput: function(input,prevent_callbacks){
		this.setValue((input.options ? input.options[input.options.selectedIndex].value : input.value),true,prevent_callbacks);
	},
	setValue: function(value,force_selected,prevent_callbacks){
		this.value = value;
		if(this.options.input){
			if(this.options.input.options){
				$A(this.options.input.options).each(function(option,i){
					if(option.value == this.value){
						this.options.input.options.selectedIndex = i;
						throw $break;
					}
				}.bind(this));
			}else
				this.options.input.value = this.value;
		}
		this.render(this.value,force_selected);
		if(!prevent_callbacks){
			if(this.options.updateUrl){
				var params = {};
				params[this.options.updateParameterName] = this.value;
				new Ajax.Request(this.options.updateUrl,{
					parameters: params
				});
			}
			this.notify('afterChange',this.value);
		}
	},
	render: function(rating,force_selected){
		(this.options.reverse ? this.links.reverse() : this.links).each(function(link,i){
			if(link.value <= Math.ceil(rating)){
				link.className = this.options.classNames[link.value <= rating ? 'on' : 'half'];
				if(this.options.rated || force_selected)
					link.addClassName(this.options.classNames.selected);
			}else
				link.className = this.options.classNames.off;
		}.bind(this));
	},
	mouseOver: function(link){
		this.render(link.value,true);
	},
	mouseOut: function(link){
		this.render(this.value);
	},
	click: function(event,link){
		this.options.rated = true;
		this.setValue((link.value ? link.value : link),true);
		if(!this.options.multiple)
			this.disable();
		if(this.options.capture){
			Event.stop(event);
			return false;
		}
	}
});
Object.extend(Control.Rating,{
	instances: [],
	findByElementId: function(id){
		return Control.Rating.instances.find(function(instance){
			return (instance.container.id && instance.container.id == id);
		});
	}
});
Object.Event.extend(Control.Rating);