/**
 * LBi SimpleMenu
 *
 * @module    simplemenu
 * @version   1.11.100216
 * @requires  LBi, jQuery
 * @author    LBi Lost Boys
 */
LBi.SimpleMenu = (function($){

	var STATE_COLD = 1;
	var STATE_HOT  = 2;
		
	var MODE_HORIZONTAL = 1;
	var MODE_VERTICAL = 2;
	var MODE_NONE = 3;

	var ARROW_LEFT = 37;
	var ARROW_UP = 38;
	var ARROW_RIGHT = 39;
	var ARROW_DOWN = 40;

	/**
	 * The SimpleMenu class provides lightweight functionality to turn a fragment of html
	 * (typically a list) into a foldout or dropdown menu. This class only adds classes and 
	 * functionality, styling must be done in CSS. Methods are called automatically, and are described
	 * here for subclassing purposes only.
	 *
	 * @class LBi.SimpleMenu
	 * @constructor
	 * @param {Node} element The root element of the menu.
	 * @param {Object} settings
	 * @return {SimpleMenu} menu
	 */
	var SimpleMenu = function(element, options) {
		this.settings = $.extend({}, SimpleMenu.Defaults, options);
		var settings = this.settings;

		this.root = element;
		this.state = STATE_COLD;
		this.stateChange = null;
		this.defaultItem = $('.' + settings.hoverClass, this.root)[0];

		$(this.root).bind('mouseover', this.mouseover.bind(this));
		$(this.root).bind('mouseout', this.mouseout.bind(this));

		if(this.keyEnabled) {
			var links = $('a', this.root);
			links.bind('focus', this.mouseover.bind(this));
			links.bind('blur', this.mouseout.bind(this));
			links.bind('keyup', this.keyup.bind(this));
		}

		if(/msie\s?6/i.test(navigator.userAgent) && settings.iframeClass) {
			this.ieFrame = $('.' + settings.iframeClass);
			if(!this.ieFrame.length) {
				this.ieFrame = $('<iframe src="about:blank" frameborder="0" class="' + settings.iframeClass + '"></iframe>');
				this.ieFrame.css({position:'absolute', opacity:0 });
				$('body').append(this.ieFrame);
			}
		}
	};

	SimpleMenu.prototype = {
		constructor: SimpleMenu,

		/**
		 * Sets a menu item's active state, by toggling an "active" class (see settings).
		 * If no animation is used, this class may be used to toggle a submenu via css.
		 *
		 * @param {Node} item Menu item, as defined by the itemSelector setting.
		 * @param {boolean} toggle
		 */
		setActive:function(item, toggle) {
			var settings = this.settings;
			var $item = $(item);
			
			$item[toggle? 'addClass' : 'removeClass'](settings.activeClass);
			
			var menu = $item.find(settings.menuSelector)[0];
			this.toggleMenu(menu, toggle);
		},
		
		/**
		 * Sets a menu item's hover state by toggling the "hover" class (see settings).
		 * This state is intended for styling purposes only, and should not be used to toggle submenu visibility.
		 *
		 * @param {Node} item Menu item, as defined by the itemSelector setting.
		 * @param {boolean} toggle
		 */
		setHover:function(item, toggle) {
			$(item)[toggle? 'addClass' : 'removeClass'](this.settings.hoverClass);
		},

		/**
		 * Toggles a submenu using the settings' animation (if any).
		 * 
		 * @param {Node} menu The submenu to toggle
		 * @param {boolean} toggle
		 */
		toggleMenu:function(menu, toggle) {
			var settings = this.settings;
			var self = this;
			
			var animation = settings.animation;
			if(animation) {
				animation.run(menu, toggle, { 
					duration: settings.animationTime,
					complete: function() {
						self.toggleIEFrame(toggle, menu);
					}
				});
			} else {
				this.toggleIEFrame(toggle, menu); 
			}
		},

		/**
		 * mouseover handler, delegates the mouseover event to a specific item
		 * @private
		 */
		mouseover: function(e) {
			var settings = this.settings;
			var item = $(e.target).closest(settings.itemSelector)[0];
			if(item && (item != this.item)) {
				this.setCurrent(item);
				clearTimeout(this.stateChange);
				var self = this;
				var toggle = function(){ 
					self.toggle(item); 
				};
				
				switch(this.state) {
					case STATE_COLD:
						this.stateChange = setTimeout(toggle, settings.openDelay);
					break;
					case STATE_HOT: 
						this.stateChange = setTimeout(toggle, settings.switchDelay);
					break;
				}
			}
		},

		/**
		 * mouseout handler, delegates the mouseout event to a specific item
		 * @private
		 */
		mouseout: function(e) {
			var settings = this.settings;
			var node = e.relatedTarget;
			while(node) {
				if(node == this.root) {
					return;
				}
				node = node.parentNode;
			}

			this.setCurrent(null);
			clearTimeout(this.stateChange);

			var self = this;
			switch(this.state) {
				case STATE_COLD: break;
				case STATE_HOT:
					this.stateChange = setTimeout(function(){ 
						self.toggle(false);
					}, settings.closeDelay);
				break;
			}
		},
		
		/**
		 * Navigates the menu using the arrow keys, if so specified in the settings.
		 * @private
		 */
		keyup: function(e) {
			var settings = this.settings;
			var key = e.keyCode;
			if(key < 37 || key > 40) {
				return;
			}
				
			var nextItem;
			var type = settings.itemSelector;
			var item = $(e.target).closest(type);
			var sibs = item.siblings(type);
			var mode = this.getOrientation(sibs);

			switch (key) {
				case ARROW_LEFT:
					nextItem = (mode == MODE_HORIZONTAL)? 
						item.prev(type) : item.parent().closest(type);
				break;
				case ARROW_UP:
					nextItem = (mode == MODE_VERTICAL)? 
						item.prev(type) : item.parent().closest(type);
				break;
				case ARROW_RIGHT:
					nextItem = (mode == MODE_HORIZONTAL)? 
						item.next(type) : item.find(type).eq(0);
				break;
				case ARROW_DOWN:
					nextItem = (mode == MODE_VERTICAL)? 
						item.next(type) : item.find(type).eq(0);
				break;
			}
			
			var next = nextItem? nextItem.find(' > a') : null;
			if(next && next.length) {
				next.trigger('focus');
				e.preventDefault();
			}
		},

		/**
		 * Returns the orientation of a menu (horizontal or vertical). Used for arrow key
		 * navigation.
		 * @private
		 */
		getOrientation: function(siblings) {
			if(siblings.length < 1) {
				return MODE_NONE;
			} else {
				return (siblings[0].offsetTop == siblings[1].offsetTop)? 
					MODE_HORIZONTAL : MODE_VERTICAL;
			}
		},

		/**
		 * Switches from menu to menu by activating a given item, and deactivating other items from the same parent.
		 * Note that toggle is ancestor based. If a sibling based approach is required (for instance when using 
		 * definition lists), you should create a subclass and override this method accordingly.
		 *
		 * @param {Node} item
		 */
		toggle: function(item) {
			var settings = this.settings;
			var tree = item? item.parentNode : this.root;
			var list;
			var lists = $(tree).find(settings.menuSelector);

			for(var i=0; i<lists.length; i++) {
				list = lists[i];
				if(list.parentNode != item) {
					this.setActive(list.parentNode, false);
				}
			}

			if(item) {
				this.defaultItem && this.setHover(this.defaultItem, false);
				list = $(item).find(settings.menuSelector)[0];
				if(list) {
					this.setActive(list.parentNode, true);
				}
			} else if(this.defaultItem) {
				this.setHover(this.defaultItem, true);
				// this.setActive(this.defaultItem, true);
			}
			
			this.state = item? STATE_HOT : STATE_COLD;
		},

		/**
		 * Toggles the iframe for IE6
		 * @private
		 */
		toggleIEFrame:function(toggle, menu) {
			if(this.ieFrame){
				if(toggle) {
					var offset = $(menu).offset();
					this.ieFrame.css({
						left: offset.left + 'px',
						top: offset.top + 'px',
						width: menu.offsetWidth + 'px',
						height: menu.offsetHeight + 'px'
					});
				} else {
					this.ieFrame.css({
						left: -9999 + 'px',
						top: -9999 + 'px'
					});
				}
			}
		},

		/**
		 * Switches from item to item by deactivating the last known one, and activating the new one.
		 * @private
		 */
		setCurrent:function(item) {
			if(this.item && this.item != this.defaultItem) {
				this.setHover(this.item, false);
			}

			this.item = item;
			if(item) {
				this.setHover(this.item, true);
			}
		}
	};

	
	/**
	 * Settings for SimpleMenu instances
	 * 
	 * @class LBi.SimpleMenu.Defaults
	 * @static
	 */
	SimpleMenu.Defaults = {
		/**
		 * Delay (in cold state) before an item is set to active state, after being hovered.
		 * @property openDelay
		 * @type number
		 * @default 500
		 */
		openDelay:   500,
		
		/**
		 * Delay (in active state) before the active state switches to the next hovered item.
		 * @property switchDelay
		 * @type number
		 * @default 200
		 */
		switchDelay: 200,
		
		/**
		 * Delay before the entire menu is closed after a complete mouseout
		 * @property closeDelay
		 * @type number
		 * @default 1000
		 */
		closeDelay:  1000,
		
		/**
		 * CSS class used by setHover to indicate hovered items.
		 * @property hoverClass
		 * @type String
		 * @default "hover"
		 */
		hoverClass:  'hover',

		/**
		 * CSS class used by setActive to indicate active items of which the submenu should be visible. 
		 * Use either this class to toggle submenu visibility, or an animation setting other than none.
		 * @property activeClass
		 * @type String
		 * @default "active"
		 */
		activeClass: 'active',

		/**
		 * CSS class for the iframe that is inserted for IE6 to cover up select boxes, set to empty or false for no frame.
		 * @property iframeClass
		 * @type String
		 * @default "ie6-iframe"
		 */
		iframeClass: 'ie6-iframe',
		
		/**
		 * Default menu item type, HTML list item by default.
		 * @property itemSelector
		 * @type String
		 * @default "li"
		 */
		itemSelector:    'li',

		/**
		 * Default submenu type, HTML list by default.
		 * @property menuSelector
		 * @type String
		 * @default "ul"
		 */
		menuSelector:    'ul',

		/**
		 * Animation for toggling submenus. See the the LBi.Animation class
		 * @property animation
		 * @type LBi.Animation
		 * @default LBi.Animation.NONE
		 */
		animation: LBi.Animation.NONE,
		
		/**
		 * The duration for the animation (if any)
		 * @property animationTime
		 * @type number
		 * @default 200
		 */
		animationTime: 200,

		/**
		 * Specifies whether the keyboard can be used to navigate the menu. Uses focus, blur 
		 * and keyup events on links found in the menu.
		 * @property keyEnabled
		 * @type boolean
		 * @default true
		 */
		keyEnabled:  true
	};
	
	/**
	 * Creates a SimpleMenu instance for all elements in the jQuery result. The settings
	 * object may be used to customize the menu's behavior. See the SimpleMenu class for details.
	 * Since this call must maintain the jQuery chain, instances are added to the 
	 * LBi.SimpleMenu.instances array.
	 *
	 * @for jQuery.functions
	 * @method simpleMenu
	 * @param {Object} settings
	 * @return {jQuery} result
	 */
	SimpleMenu.instances = $.registerPlugin('simpleMenu', SimpleMenu);

	return SimpleMenu;

})(jQuery);
