/**
 * Dynamic Drop-Down jQuery Plug-in - Compatible with jQuery-1.2.6.js
 *
 * @author Karl Stanton (Fi)
 * @version 2.1
 * @version 3.0 jsLint compatible
 * @version 3.1 Added Options with 2 Line Compatability
 *
 * Usage: 
 *   - Create Select form element, and set an ID.
 *   - Make sure the Select element has data, or else the plug-in will display an empty default option
 *   - Write your styles in jquery-dynamic-drop.css, ensuring you name a namespace that match each or all elements
 *   - In a 'document ready' function, execute the plug in as follows:
 *   
	 $(document).ready(function () {
 		$('#idoptsf_select_element').dropDown({
 			css: [title of CSS class namespace], 
			width: [width of the drop down when invoked],   <-- This can be set dynamically in the css namespace, however different browsers seem to be
																rendering css/javascript at different intervals, so a variable is safest option
			height: [height of drop down when invoked], 
			defaulttxt: [the default select text], 
			timeout: [timeout (in milliseconds) before revoked]
		});
	});
 * 
 *
 * Order of programming execution:
 *   - Load in style-sheet (jquery-dynamic-drop.css)
 *   - Patch jQuery 1.2.6 height function
 *   - Bind the select element's <option> data to an array
 *   - Build dynamic html elements (div, wrapper, ul, li) and apply array data, style-sheet and other mandatory/read-only css requirements
 *   - Bind event listeners
 *
**/
// Drop Down
(function ($) {
	$.fn.dropDown = function ($options) {

		// Set our default options
		var $defaults = {
			css: null, 				// CSS Namespace for all dynamic elements
			width: 400, 			// width
			defaultheight: null, 	// height of drop-down when invoked
			defaulttxt: null, 		// default message
			li_height: 28,			// height of the LI when styled.
			timeout: 1000, 			// timeout in milliseconds before revoked
			zDepth: 4000 			// depth of dropdown overlay
		};

		// extend the options
		var $opts = jQuery.extend($defaults, $options);
		
		// bring the options to the pagination object
		for (var i in $opts) {
			if (i) {
				jQuery.dropDown[i] = $opts[i];	
			}
		}

		return this.each(function () {
			// Define our element
			var el = jQuery(this);
			var opts = jQuery.meta ? jQuery.extend({}, $opts, el.data()) : $opts;
			var defaultid = "default-" + el.attr("id");
			var dropid = "drop-" + el.attr("id");
			opts.dropid = dropid;
			
			// Replace select with the styled div		
			el.before('<div id="' + defaultid + '" class="' + opts.css + '"></div>');
			
			//el.hide(); not working on hidden elements in Webkit-based browsers, use css() instead.
			el.css({display: 'none'});
			
			var def = $("#" + defaultid);
			opts.def = def;
			
			def.append("<span></span>");		
			def.after('<ul id="' + dropid + '" class="' + opts.css + '" style="display:block;"></ul>').width(opts.width);
			
			// The Drop Down Object
			var ul = $("#" + dropid);
			opts.ul = ul;
			
			// Add the wrapper with contains FiQ business, drop shadows, etc.
			ul.wrap("<div id=\"wrap-" + defaultid + "\" class=\"" + opts.css + "_wrapper\" style=\"margin-top: 0px\">").hover(function () {
				jQuery.dropDown.stopTimer(opts);
			}, function () {
				jQuery.dropDown.beginTimer(opts);				
			});
			var wrapper = $("#wrap-" + defaultid);
			opts.wrapper = wrapper;
	
			// Create our value array
			var arr = jQuery.makeArray(el.children());

			// The data object
			var options = {
				el: el, 
				css: opts.css,
				defaultid: defaultid, 
				def: def, 
				zdepth: opts.zDepth,
				dropid: dropid, 
				ul: ul, 
				drop_open: false, 
				wrapper: wrapper, 
				li_height: opts.li_height,
				arrlength: arr.length, 
				timerID: null
			};
			
			// Bind data to the element
			for (i = 0; i > opts.length; i++) {
				options[i]  = opts[i];
			}
			jQuery.data(el.get(0), "options", {	options: options });


			// Check to see if we have any data...
			if (arr.length > 0) {
				// Set the default text
				if (opts.defaulttxt !== null) {
					jQuery("span", def).text(opts.defaulttxt);
				} else {
					// Default to the first text, unless we find a selected element
					jQuery("span", def).text(arr[0].text);
					// Or find a selected element
					for (i = 0; i < arr.length; i++) {
						if ($(arr[i]).attr("selected")) {
							jQuery("span", def).text(arr[i].text);
						}
					}
				}
				// Now append the list data with the data we obtained earlier before the build(), and skip the first (default) entry
				for (i = 0; i < arr.length; i++) {
					
					// ------------------------------------------------------------
					// I think creating two elements is unnecessary, so it should
					// be a little more optimized to just create one and not have
					// jQuery do any additional append() logic calls.  --Kevin
					// ------------------------------------------------------------
					
					/*
					var li = $("<li>");
					// First LI / Span has no border for styling reasons
					var span;
					*/
					
					// ------------------------------------------------------------
					// By ordering it like this, we ALWAYS jump to the else 
					// class arr.length - 1 times (not as optimized). --Kevin
					// ------------------------------------------------------------
					
					/*
					if (i === 0) {
						span = "<span style=\"border-top: 0px;\">" + arr[i].text + "</span>";
					} else {
						span = "<span>" + arr[i].text + "</span>";
					}
					*/
					
					// ------------------------------------------------------------
					// Attaching events to every span is probably what's causing
					// the biggest slow down. Instead, let's attach one event to 
					// the parent and just grab the event target to apply our 
					// mouse events to.  --Kevin
					//
					// D'oh! Of course we need a hover event on the individual 
					// list items. But we don't need to have them for the click
					// event at least.  --Kevin
					// 
					// I see you wanted to still be able to get the current selection
					// via keyboard it looks like so maybe that's why you didn't 
					// attach via the UL. If you have time to get keyboard
					// working again (so that the contents scroll), perhaps what
					// you can do is hook onto the change event of the actual 
					// select component to get it that way. For now, I'm going to
					// leave the event tied to the parent UL
					// ------------------------------------------------------------
					
					/*(function () {
						li.append(span).bind("click", function () {
								jQuery.dropDown.makeSelection(options);
							}).hover(
								function () {
									// Halt our timer
									jQuery.dropDown.stopTimer(options);
									// Update our currently selected index (so we can sync mouse and keyboard interactions)
									// Now apply the style
									jQuery.dropDown.resetHover(options);
									if (jQuery.dropDown.currLi(options, this) === 0) {
										$(this).addClass("first_hover");
									} else {
										var cls = jQuery.dropDown.determineHeight(options, $(this));
										$(this).addClass(cls);
									}
								},
								function () {
									jQuery.dropDown.resetHover(options);
								}
							); 
					}());
					ul.append(li);*/
					
					// ---------------------------------------------------------
					
					// Optimized version
					var list_el;
					if (i > 0) {
						list_el = "<li><span>" + arr[i].text + "</span></li>";
					}
					else {
						list_el = "<li><span style=\"border-top: 0px;\">" + arr[i].text + "</span></li>";
					}
					ul.append(list_el);
					
				}
				
				// ------------------------------------------------------------
				// Breaking this out of the for loop. Events were getting 
				// triggered on every single element because each element got 
				// the event attached multiple times.  --Kevin
				// ------------------------------------------------------------
				
				(function () {
					ul.children('li').hover(
						
						// mouse over
						function (e) {
							
							// Halt our timer
							jQuery.dropDown.stopTimer(options);
							
							// Now apply the style
							jQuery.dropDown.resetHover(options); // TODO: Why are we calling this both here and the mouseout event?
							
							// ------------------------------------------------------------
							// If the keyboard functionality doesn't make it in, then 
							// liHighlight is the only other method using this 
							// determineHeight() function. In that case, avoid the extra
							// function call and just perform check here for more 
							// optimization.  --Kevin
							// ------------------------------------------------------------

							var cls = jQuery.dropDown.determineHeight(options, $(this));
							if (jQuery.dropDown.currLi(options, this) <= 0) {
								if (cls === "hover") {
									cls = "first_hover";
								}
							}
							$(this).addClass(cls);
						},
						
						// mouse out
						function (e) {
							jQuery.dropDown.resetHover(options); // TODO: Look into optimizing this method (see Kevin's comment)
						}
						
					);
					ul.click(function (e) {
						jQuery.dropDown.makeSelection(options);
					});
					
				}());
				
			} else {
				def.html('&nbsp;');
				ul.append('<li>&nbsp;</li>');
			}
			
			// Actual Height of the Drop Down
			// opts.li_height is the height of the LI (EA.com specific)
			// Set restrictive height only if current list exceeds maximum height set in options
			if (opts.defaultheight !== null) {
				if ((arr.length * opts.li_height) > opts.defaultheight) {
					ul.css({
						overflow: "auto", 
						"height": opts.defaultheight, 
						"width": opts.width + 2
					});
					ul.find("li span").width(opts.width - opts.li_height);
				}
			}
	
			// Set Events //
			/*jQuery("span",def).bind("click", {el: el}, jQuery.dropDown.dropClick).hover(
				function () {
					jQuery.dropDown.stopTimer(opts);
					$(this).parent().removeClass(opts.css).addClass(opts.css + "_hover");
				}, 
				function () {
					jQuery.dropDown.resetTimer(opts);
					jQuery.dropDown.beginTimer(opts);
					$(this).parent().removeClass(opts.css + "_hover").addClass(opts.css);
				}
			);*/
			def.bind("click", {el: el}, jQuery.dropDown.dropClick).hover(
				function () {
					jQuery.dropDown.stopTimer(opts);
					$(this).removeClass(opts.css).addClass(opts.css + "_hover");
				}, 
				function () {
					jQuery.dropDown.resetTimer(opts);
					jQuery.dropDown.beginTimer(opts);
					$(this).removeClass(opts.css + "_hover").addClass(opts.css);
				}
			);
			
			// Close on clickoutside
			jQuery(document).click(function (e) {
				// If the clicked target isn't the active dropdown, and if the dropdown is even open...
				var id;
				if (e.target.tagName === "SPAN") {
					id = jQuery(e.target).parent().attr("id");
				} else {
					id = e.target.id;
				}
				if (id !== defaultid && options.drop_open) {
					jQuery.dropDown.closeDrop(options);
				}
			});
			
			// Key Captures
			/*jQuery(document).keydown(function (e) {
				// Because we wrap these events to the window (as there's no actual input on the dropdown), we must then only act on the currently opened dropdown
				if (el.data("options").options.drop_open) {
					// What do we do when we hit...
					// Check to see if the drop is open
					switch (e.which) {
						// Enter - Makes the users selection
					case(13):
						jQuery.dropDown.makeSelection(options);
						// Stops a form from being processed if a submit button exists
						return false;
						// ESC - Closes the Dropdown
					case(27):
						jQuery.dropDown.closeDrop(options);
						// Prevent default window interaction
						return false;
						// Up arrow - Set the next LI up
					case(38):
						jQuery.dropDown.liHighlight(options, 0);
						// Prevent default window interaction
						return false;
						// Down arrow - Set the next LI down
					case(40):
						jQuery.dropDown.liHighlight(options, 1);
						// Prevent default window interaction
						return false;
					}
				}
			});
			
			// If the user has been playing with the up/down keys, reset and restart the timer when they stop
			$(document).keyup(function (e) {
				if ((e.which === 38 || e.which === 40) && el.data("options").options.drop_open) {
					jQuery.dropDown.resetTimer(options);
					jQuery.dropDown.beginTimer(options);
				}
			});*/

			
		});
	};
	
	$.extend({dropDown: {
		// Determines the action of our click. Open or close
		dropClick: function (e) {
			var opts = e.data.el.data("options").options;
			if (opts.drop_open) {
				jQuery.dropDown.closeDrop(opts);
			} else {
				jQuery.dropDown.openDrop(opts);
			}
		}, 
		
		checkDropDirection : function (opts) {
			// Determine which direction to open by adding the top offset of thet default selection div to the height of the dropdown.
			// If we can't clear the bottom bound (determined by the document height), then open *above* the default selection
			
			var h = opts.wrapper.outerHeight();
			
			// Browser Scroll Offset
			var scrollOffset = Number(opts.def.offset().top) - EA.framework.getScrollXY()[1];
			
			// Default to drop down (underneith the drop-down div)
			// 23 = height of drop selector. Font-size and other padding messes with the dynamic grab of this
			// value across different areas in EA due to nesting
			//opts.def.outerHeight(true)
			var elementTop = scrollOffset + 23;
			
			// Placement on the DOM (relative top position, -1 to offset border)
			var DOMtop = opts.def.position().top + 23 - 1;
			
			// So, is it going to exceed the bounds? Let's first find out both ways first (33 = Height of GUS)
			var drop_down = ($(window).height() - 33) - (elementTop + h);
			var drop_up   = opts.def.offset().top - h;//(elementTop - h) - $(window).scrollTop();

			// Do we exceed the bottom bound?
			var changedCss = false;
			if (drop_down <=  0) {
				// Before we decide to pop it up top, we must find out if there is even enough space to do so, if there isn't then we default back to dropping it down
				// Now let's see if the drop_up value is greater than 0, ie: will it also go outside our top bounds.... we only want drop up if we have more "room" aka a positive margin
				if (drop_up <=  0) {
					changedCss = $.dropDown.switchStyles(opts, 0);
				} else {
					DOMtop = opts.def.position().top - h;
					changedCss = $.dropDown.switchStyles(opts, 1);
				}
			} else {
				changedCss = $.dropDown.switchStyles(opts, 0);
			}

			// Now for the left hand side position -2 to offset the drop shadow
			var left = opts.def.position().left - 2;
			// Fix Webkit bug and it's calculation problems (http://fixunix.com/1626217-post5.html)
			if (navigator.userAgent.indexOf('Firefox/3') !== -1 || navigator.userAgent.indexOf('WebKit') !== -1) {
				if (document.documentElement.clientWidth % 2 === 1) {
					//window.resizeBy(-1, 0);
					left++;
				}
			}
			
			if (changedCss) {
				return $.dropDown.checkDropDirection(opts);
			} else {
				return [DOMtop, left];
			}
			
		}, 
		
		// Opens the drop
		openDrop : function (opts) {
			var position = $.dropDown.checkDropDirection(opts);
			
			// Highlight the first LI
			//opts.ul.children("li:first").addClass("first_hover");

			// Set our new properties and display it 
			opts.wrapper.css({
				"top": position[0], 
				"left": position[1], 
				"z-index": opts.zdepth, 
				"visibility": "visible", 
				"display": "block"
			});

			jQuery.dropDown.resetTimer(opts);
			opts.drop_open = true;
		}, 

		// Switches CSS styles
		switchStyles: function (opts, i) {
			// Yes, so change the CSS
			if (i) {
				// Going up...
				opts.def.removeClass(opts.css + "_hover").addClass(opts.css + "_on_Up");
				if (!opts.wrapper.hasClass(opts.css + "_wrapper_Up")) {
					opts.wrapper.removeClass(opts.css + "_wrapper").addClass(opts.css + "_wrapper_Up");
					return true;
				} else {
					return false;
				}
			} else {
				// Going down...
				opts.def.removeClass(opts.css + "_hover").addClass(opts.css + "_on");
				if (!opts.wrapper.hasClass(opts.css + "_wrapper")) {
					opts.wrapper.removeClass(opts.css + "_wrapper_Up").addClass(opts.css + "_wrapper");
					return true;
				} else {
					opts.wrapper.removeClass(opts.css + "_wrapper_Up").addClass(opts.css + "_wrapper");
					return false;
				}
			}
		}, 

		// Closes the drop
		closeDrop : function (opts) {
			// Reset default
			opts.def.removeClass(opts.css + "_on").removeClass(opts.css + "_on_Up").addClass(opts.css);
			// Hide it and teset the scroll position of the div for unforgiving browsers that wish to remember scrollPositions of hidden divs
			opts.ul.scrollTop(0);
			opts.wrapper.css({
				"visibility": "hidden", 
				"display": "block"
			});
			// Remove the current focus var and drop var
			opts.current_focus = null;
			opts.drop_open = false;
			jQuery.dropDown.resetHover(opts);
			jQuery.dropDown.stopTimer(opts);
			//}
		}, 
		
		// ---------------------------------------------------------------------
		// Would it be faster to supply a more detailed selector?  --Kevin
		// Example: opts.ul.children(*[class="hover"]).removeClass("hoverLarge").removeClass("first_hover");
		// ---------------------------------------------------------------------
		
		// Removes all the hover classes
		resetHover : function (opts) {
			opts.ul.children().removeClass("hover").removeClass("hoverLarge").removeClass("first_hover");
		}, 
		
		// Returns the currently selected LI
		currLi : function (opts, li) {
			var el = jQuery("#" + opts.dropid + " li");
			var curr_li = jQuery(el).index(li);
			if (curr_li === -1) {
				curr_li = 0;
			}
			return curr_li;
		}, 
		
		// Decide which class to apply, single line or 2 lines...
		determineHeight : function (opts, el) {
			if (el.height() <= opts.li_height) {
				return "hover";
			} else {
				return "hoverLarge";
			}
		},
		
		// Highlights the selected LI
		liHighlight : function (opts, d) {
			// D = direction (up 0 / down 1)
			var curr_li = jQuery.dropDown.currLi(opts, jQuery("#" + opts.dropid + " li.hover"));
			if (curr_li === 0) {
				curr_li = jQuery.dropDown.currLi(opts, jQuery("#" + opts.dropid + " li.hoverLarge"));
			}
			var cls;
			// If up...
			if (!d) {
				if (curr_li - 1 >=  0) {
					// Determine which class to show
					var li = jQuery("#" + opts.dropid + " li:eq(" + (curr_li - 1) + ")");
					if (curr_li - 1 > 0) {
						cls = jQuery.dropDown.determineHeight(opts, li);
					} else {
						cls = "first_hover";
					}
					jQuery.dropDown.resetHover(opts);
					li.addClass(cls);
					// Are we exceeding the overflow?
					jQuery.dropDown.liCheckScroll(d, li, opts);
				}
			} else {
				// If down...
				if (curr_li + 1 < opts.arrlength) {
					jQuery.dropDown.resetHover(opts);
					li = jQuery("#" + opts.dropid + " li:eq(" + (curr_li + 1) + ")");
					// Decide which class to apply, single line or 2 lines...
					cls = jQuery.dropDown.determineHeight(opts, li);
					li.addClass(cls);
					// Are we exceeding the overflow?
					jQuery.dropDown.liCheckScroll(d, li, opts);
				}
			}
		}, 
		
		// Checks to see if the next LI to be selected will be outside of the UL's overflow. If it is, scroll it
		liCheckScroll : function (d, curr_li, opts) {
			if (!d) {
				// If our next position will be less than the top of our containing parent
				if (curr_li.position().top < 0) {
					// Set the scroll position to be it's current position - the height of the next LI
					opts.ul.scrollTop(opts.ul.scrollTop() - curr_li.height());
				}
			} else {
				// If our current position + the height of the NEXT LI exceeds our boundaries
				if ((curr_li.position().top + curr_li.height()) >= opts.height) {
					// Set the scroll position to be it's current position + the height of the next LI
					opts.ul.scrollTop(opts.ul.scrollTop() + curr_li.height());
				}
			}
		}, 
		
		// Makes the selection
		makeSelection: function (opts) {
			var curr_li = jQuery.dropDown.currLi(opts, jQuery("#" + opts.dropid + " li.hover"));
			if (curr_li === 0) {
				curr_li = jQuery.dropDown.currLi(opts, jQuery("#" + opts.dropid + " li.hoverLarge"));
			}
			
			// First, change the default value to reflect the new selection. We get this value from the <option>[value]</option>
			var text = jQuery("li:eq(" + (curr_li) + ")", opts.ul).text();
			jQuery("span", opts.def).html(text);
			
			// Now, we must reflect the change in our original select object as it's used for form submission
			// We can do this by finding out our position in the UL and replicate that against the selectedIndex
			jQuery(opts.el).get(0).selectedIndex = curr_li;
			jQuery(opts.el).trigger("change");
			
			// Close the drop
			jQuery.dropDown.closeDrop(opts);
		}, 
		
		
		
		
		// Timer Functions
		// Runs the the search results pane timeout
		beginTimer : function (opts) {
			if (window.secs === 0) {
				jQuery.dropDown.closeDrop(opts); // run the close function
			} else {
				window.secs--;
				window.timerID = self.setTimeout(function () {
					jQuery.dropDown.beginTimer(opts);
				}, 1000);
			}
		}, 
		// Resets the timer
		resetTimer : function (opts) {
			window.secs = opts.timeout / 1000;
		}, 
		// Resets and clears the timer
		stopTimer : function (opts) {
			jQuery.dropDown.resetTimer(opts);
			clearTimeout(window.timerID);
		},
		// Resets the dropdown to it's original state. Pass in el
		reset : function (el) {
			jQuery(el).each(function () {
				var opts = jQuery(this).data("options").options;
				jQuery(this).get(0).selectedIndex = 0;
				if (opts.defaulttext !== null) {
					jQuery("span", opts.def).text(jQuery(this).get(0)[0].text);
				} else {
					jQuery("span", opts.def).text(opts.defaulttext);
				}
			});
		}
	}});
	
})(jQuery);