/*
Script: Plugin.OrangeCow.InteractiveElements.js
	Contains classes relating to interactive elements.

Author:
	Ionut Costica
	<http://www.nocowsland.com>
	<ionut.costica@gmail.com>

License:
	MIT
*/

/*
Class: Fx.Morph
	A class that morphs an element from one style to another

Author:
	Valerio <http://www.mad4milk.net>, with modifications by Ionut Costica <http://www.nocowsland.com>
*/
Fx.Morph = Fx.Styles.extend({
	crtClass: null,
	
	initialize: function(element, startClass, options) {
		this.element = $(element);
		this.element.addClass(startClass);
		this.crtClass = startClass;
		
		this.parent(element, options);
	},
	
	start: function(className) {
		if(className && className!='') {
			if(this.options.duration>0) {
				var to = {};
		 
				$each(document.styleSheets, function(style) {
					var rules = style.rules || style.cssRules;
					$each(rules, function(rule) {
						if(!rule.selectorText.test('\.' + className + '$')) return;
						Fx.CSS.Styles.each(function(style) {
							if(!rule.style || !rule.style[style]) return;
							var ruleStyle = rule.style[style];
							to[style] = (style.test(/color/i) && ruleStyle.test(/^rgb/)) ? ruleStyle.rgbToHex() : ruleStyle;
						});
					});
				});
			}
			
			(function() {
				this.element.removeClass(this.crtClass);
				this.element.addClass(className);
				this.crtClass = className;
			}).bind(this).delay(this.options.duration);
			
			this.stop();
			if(this.options.duration>0) return this.parent(to);
		}
		return null;
	}
});

Fx.CSS.Styles = ["backgroundColor", "backgroundPosition", "color", "width", "height", "left", "top", "bottom", "right", "fontSize", "letterSpacing", "lineHeight", "textIndent", "opacity"];
Fx.CSS.Styles.extend(Element.Styles.padding);
Fx.CSS.Styles.extend(Element.Styles.margin);
Element.Styles.border.each(function(border) {
	['Width', 'Color'].each(function(property) {
		Fx.CSS.Styles.push(border + property);
	});
});

var clicked = {};
/*
Class: InteractiveElement
	A class that refers to interactive elements and changes them appropriately.

Example:
	(start code)
	new InteractiveElement('element_id', {
		styleClasses: {
			normal: 'normal',
			over: 'over',
			clicked: 'clicked'
		}
	});
	(end)
*/
var InteractiveElement = new Class({
	/*
	Properties: options
	
	styleClasses		- The names of the CSS classes used for the element: base (will always be active), normal
							(active when the element is neither clicked, nor over), over and clicked.
							_Defaults to null for every classname_. *Required for effect to work*
	onNormal			- The function to execute when the element returns to its normal state.
							_Defaults to an empty function_
	onOver				- The function to execute when starting to hover over the element.
							_Defaults to an empty function_
	onClick				- The function to execute when clicking the element.
							_Defaults to an empty function_
	onMouseDown			- The function to execute on mouse down
							_Defaults to an empty function_
	onHoldMouseDown		- The function to execute periodically when holding the mouse down
							_Defaults to an empty function_
	onMouseUp			- The function to execute when releasing the mouse
							_Defaults to an empty function_
	holdMouseDownDelay	- The delay between consecutive executions of the onHoldMouseDown function
							_Defaults to 50ms_
	outDelay			- The delay in ms during which a pair of (leave, enter) messages are ignored.
							_Defaults to 100ms_
	morphOptions		- Fx.Styles options used for the class morph.
							_Defaults to { duration: 300, transition: Fx.Transitions.linear }_
	group				- The name of the group of which the element is part of.
							Used for remembering which element in the group is clicked, and not firing over & out
							messages for it.
							_Defaults to 'default'_
	sticky				- If the element is clicked, do we want to show the user that it is selected somehow?
							_Defaults to true_
	*/
	options: {
		styleClasses: {
			base: '',
			normal: '',
			over: '',
			clicked: ''
		},
		
		onNormal:			Class.empty,
		onOver:				Class.empty,
		onClick:			Class.empty,
		onMouseDown:		Class.empty,
		onMouseUp:			Class.empty,
		onHoldMouseDown:	Class.empty,
		onMouseWheel:		Class.empty,
		
		args: {
			onNormal:			[],
			onOver:				[],
			onClick:			[],
			onMouseDown:		[],
			onMouseUp:			[],
			onHoldMouseDown:	[],
			onMouseWheel:		[]
		},
		
		holdMouseDownDelay: 50,
		
		outDelay: 100,
		
		morphOptions: {
			duration: 300,
			transition: Fx.Transitions.linear
		},
		
		group: 'default',
		sticky: true
	},
	
	element: null,
	morpher: null,
	hndTimer: null,
	hndMDnTimer: null,
	
	initialize: function(element, options) {
		this.options.args = $merge(this.options.args);
		this.setOptions(this.options, options);
		
		this.element = $(element);
		this.element.addClass(this.options.styleClasses.base);
		
		// weird mumbo-jumbo to make sure that the options.args is not a pointer to something (see the $merge above, too)
		$each(this.options.args, function(value, key) { this.options.args[key] = [this.element].extend(value); }, this);
		
		this.morpher = new Fx.Morph(this.element, this.options.styleClasses.normal, this.options.morphOptions);
		
		this.element.addEvents({
			'mouseenter': function(event) {
				if(clicked[this.options.group]!=this.element) {
					if(this.hndTimer) {
						$clear(this.hndTimer);
					} else {
						this.morpher.start(this.options.styleClasses.over);
						this.fireEvent('onOver', this.options.args.onOver);
//						this.options.onOver(this.element);
					}
				}
			}.bind(this),
			
			'mouseleave': function(event) {
				if(clicked[this.options.group]!=this.element) {
					if(this.hndTimer) $clear(this.hndTimer);
					this.hndTimer = (function() {
						this.morpher.start(this.options.styleClasses.normal);
						$clear(this.hndTimer);
						this.fireEvent('onNormal', this.options.args.onNormal);
//						this.options.onNormal(this.element);
					}).bind(this).delay(this.outDelay);
				}
			}.bind(this),
			
			'click': function(event) {
				if(this.options.sticky) {
					if(clicked[this.options.group]!=this.element) {
						var oldElem = clicked[this.options.group];
						this.morpher.start(this.options.styleClasses.clicked);
						clicked[this.options.group] = this.element;
						if(oldElem) oldElem.fireEvent('mouseleave', null);
					}
				}
				this.fireEvent('onClick', this.options.args.onClick);
//				this.options.onClick(this.element);
			}.bind(this),
			
			'mousedown': function(event) {
				this.fireEvent('onMouseDown', this.options.args.onMouseDown);
				this.fireEvent('onHoldMouseDown', this.options.args.onHoldMouseDown);
//				this.options.onMouseDown(this.element);
//				this.options.onHoldMouseDown();
				
				if(this.hndMDnTimer) $clear(this.hndMDnTimer);
				this.hndMDnTimer = this.fireEvent.periodical(this.options.holdMouseDownDelay, this, ['onHoldMouseDown', this.options.args.onHoldMouseDown]);
//				this.hndMDnTimer = this.options.onHoldMouseDown.periodical(this.options.holdMouseDownDelay);
			}.bind(this),
			
			'mouseup': function(event) {
				$clear(this.hndMDnTimer);
				this.fireEvent('onMouseUp', this.options.args.onMouseUp);
//				this.options.onMouseUp(this.element);
			}.bind(this),
			
			'mousewheel': function(event) {
				var e = new Event(event);
				e.stop();
				
				this.fireEvent('onMouseWheel', this.options.args.onMouseWheel.copy().extend([e.wheel]));
//				this.options.onMouseWheel(this.element, e.wheel);
			}.bind(this)
		});
	},
	morph: function(className) {
		this.morpher.start(className);
	}
});

InteractiveElement.implement(new Events, new Options);

/*
Function: InteractiveElementGroup
	Helper function that automatically creates the InteractiveElements for an array of elements.
	
Usage:
	Same usage as new InteractiveElement(element, options), just that the first argument is an array.
*/
function InteractiveElementGroup(elements, options) {
	elements.each(function(element, index) {
		new InteractiveElement(element, options);
	});
}
