(function($) {
	$.fn.dropdown = function( setup ) {
		var dropdowns = null;
		var hoveredDropdown = null;
		var tweenTypes = ["none", "slide", "box", "fade"];
		
		var dataNames = {
			dropdown : "dropdown",
			display : "display",
			arrow : "arrow",
			list : "list",
			selectbox : "selectbox",
			closed : "closed"
		}
		
		var setup = jQuery.extend({
			tweenSpeed : 100,
			closeSpeed : null,
			openSpeed : null,
			displayTweenSpeed : 150,
			
			tweenType : 1,
			closeType : null,
			openType : null,
			
			hideFirstOption : false,
			animateDisplay : false,
			animateDisplayOnStartUp : false,
			
			listDistance : 0,
			
			className : "",
			selectedClassName : "sel",
			mainClassName : "dropdown",
			displayClassName : "display",
			arrowClassName : "arrow",
			listClassName : "",
			hiddenClassName : "hidden",
			listBaseClassName : "dropdown-list",
			optionIndexPrefix : "opt-"
		}, setup);
		
		
		function buildDropdown( sbNode ) {
			if( sbNode.nodeName.toLowerCase() == "select" ) {
				var d = document;
				var sb = $(sbNode);
				var optCount = sbNode.options.length;
				
				var list = $('<div class="'+ setup.listBaseClassName +'"><ul></ul></div>').addClass(setup.listClassName);
				var ul = $("ul:first", list);
				
				for( var i=((setup.hideFirstOption)? 1 : 0); i < optCount; i++ ) {
					var opt = $(sbNode.options[i]);
					var optText = opt.text();
					
					var a = $(d.createElement("a")).attr("href", "#"+ opt.val()).text( optText );
					var li = $(d.createElement("li")).append(a).addClass(setup.optionIndexPrefix + i);
					
					ul.append(li);
				}
				
				var div_dd = $( (d.createElement("div")) )
				
				with(div_dd) {
					data(dataNames.selectbox, sbNode);
					addClass(setup.mainClassName);
					addClass(setup.className);
					insertBefore(sb);
					append( $( (d.createElement("div")) ).addClass(setup.displayClassName) );
					append( $( (d.createElement("div")) ).addClass(setup.arrowClassName) );
					append(list);
					append( $( (d.createElement("div")) ).addClass(setup.hiddenClassName).append(sb) );
				}
				
				initDropdown(div_dd);
			}
		}
		
		function initDropdown( dd ) {
			var dd = (dd)? $(dd) : $(this);
			
			dd.data(dataNames.display, $("."+ setup.displayClassName, dd));
			dd.data(dataNames.arrow, $("."+ setup.arrowClassName, dd));
			dd.data(dataNames.list, $("."+ setup.listBaseClassName, dd));
			
			var list = dd.data(dataNames.list);
			
			$("li a", list).data(dataNames.dropdown, dd).click( optionClick );
			
			var disarr = dd.data(dataNames.display).add( dd.data(dataNames.arrow) ).click( toggleDropdown );
				disarr.add( list ).data(dataNames.dropdown, dd);
				
			dd.mouseenter( function() { hoveredDropdown = $(this); } );
			dd.mouseleave( function() { hoveredDropdown = null; } );
			
			$(document).click( function() { closeDropdowns(); } );
			
			dd.data(dataNames.closed, true);
			
			addDropdown(dd);
			moveList(dd);
			setSelectedIndexText(dd);
			calcWidth(dd);
			markSelectedOption(dd);
		}
		
		function toggleDropdown() {
			var dd = $(this).data(dataNames.dropdown);
			var closed = dd.data(dataNames.closed);
			
			if( closed ) {
				openDropdown(dd);
			} else {
				closeDropdown(dd);
			}
		}
		
		function openDropdown(dd) {
			var dd = (dd)? $(dd) : $(this);
			
			if(dd.data(dataNames.closed) == undefined)
				dd.data(dataNames.closed, true);
			
			if( dd.data(dataNames.closed) ) {
				dd.addClass(setup.className +"-open");
				positionList(dd);
				tweenDropdown(dd, false);
				dd.data(dataNames.closed, false);
			}
		}
		
		function closeDropdown( dd ) {
			var dd = (dd)? $(dd) : $(this);
			
			if(dd.data(dataNames.closed) == undefined)
				dd.data(dataNames.closed, false);
			
			if( !dd.data(dataNames.closed) ) {
				dd.removeClass(setup.className +"-open");
				tweenDropdown(dd, true);
				dd.data(dataNames.closed, true);
			}
		}
		
		function tweenDropdown( dd, hide, onEnd ) {
			var list = dd.data(dataNames.list);
			var tween = getTween(hide);
			
			eval("list."+ tween.name +"("+ tween.speed +", onEnd)");
		}
		
		function closeDropdowns() {
			if( dropdowns ) {
				var filtered = $( dropdowns );
				
				if(hoveredDropdown) {
					filtered = filtered.not(hoveredDropdown);
				}
				
				filtered.each( function() { closeDropdown(this); } );
			}
		}
		
		function optionClick( e ) {
			var opt = $(this);
			var optText = opt.text();
			var dd = opt.data(dataNames.dropdown);
			var sb = dd.data(dataNames.selectbox);
			var li = opt.parent();
			var cn = li.attr("class");
			var cnexp = /\bopt-(\d+)\b/;
			var m = cn.match(cnexp);
			
			if( m ) {
				sb.selectedIndex = m[1];
				optText = $(sb.options[ sb.selectedIndex ]).text();
			}
			
			markSelectedOption(dd, function() { closeDropdown(dd); });
			setDisplayText(dd, optText);
			
			$(sb).change();
			
			e.preventDefault();
			e.stopPropagation();
		}
		
		function markSelectedOption( dd, callback ) {
			var sb = dd.data(dataNames.selectbox);
			var index = sb.selectedIndex;
			
			dd.data(dataNames.list).children("li").removeClass(setup.selectedClassName).filter("."+ setup.optionIndexPrefix + index).addClass(setup.selectedClassName);
			
			if( typeof(callback) == "function" )
				callback();
		}
		
		function setSelectedIndexText( dd ) {
			var sp = dd.data(dataNames.selectbox);
			var text = $(sp.options[ sp.selectedIndex ]).text();
			
			setDisplayText(dd, text);
		}
		
		function setDisplayText( dd, text ) {
			var disp = dd.data(dataNames.display);
			
			if( setup.animateDisplay ) {
				disp.fadeOut(setup.displayTweenSpeed, function() {
					$(this).text(text).fadeIn(setup.displayTweenSpeed);
				})
			} else
				disp.text( text );
			
		}
		
		function getDisplayText( dd ) {
			return dd.data(dataNames.display).text();
		}
		
		function calcWidth( dd ) {
			var list = dd.data(dataNames.list).css({"display" : "block", "visibility" : "hidden"});
			var options = $("option", dd.data(dataNames.selectbox));
			var disp = dd.data(dataNames.display);
			
			var initText = disp.text();
			var maxW = disp.fadeOut(1).width();
			
			var widths = [];
			
			options.each(function() {
				disp.text( $(this).text() );
				widths.push($(this).text() +" :: "+ disp.width())
				maxW = Math.max(maxW, disp.width());
			})
			
			var fadeSpeed = (setup.animateDisplay && setup.animateDisplayOnStartUp)? setup.displayTweenSpeed : 1;
			disp.text( initText ).width( maxW ).fadeIn(fadeSpeed);
			
			dd.data(dataNames.list).width( dd.width() ).css({"display" : "none", "visibility" : "visible"});
		}
		
		function getTweenType( hide ) {
			var ti = (hide && setup.closeType != null)? setup.closeType : ((!hide && setup.openType != null)? setup.openType : setup.tweenType);
			var typeIndex = Math.max(0, Math.min(tweenTypes.length, ti));
			return tweenTypes[typeIndex];
		}
		
		function getTweenSpeed( hide ) {
			var speed = (hide && setup.closeSpeed != null)? setup.closeSpeed : ((!hide && setup.openSpeed != null)? setup.openSpeed : setup.tweenSpeed)
			return Math.max(0, speed);
		}
		
		function getTween( hide ) {
			var type = getTweenType( hide );
			var tweenSpeed = getTweenSpeed( hide );
			var tweenName = "";
			
			switch(type) {
				case "slide":
					tweenName = "slide"+ ((hide)? "Up" : "Down");
					break;
				case "fade":
					tweenName = "fade"+ ((hide)? "Out" : "In");
					break;
				case "none":
					tweenSpeed = 0;
				case "box":
				default:
					tweenName = (hide)? "hide" : "show";
					break;
			}
			
			return {name : tweenName, speed : tweenSpeed};
		}
		
		function moveList( dd ) {
			$(dd.data(dataNames.list)).appendTo($("body"));
			positionList(dd);
		}
		
		function positionList( dd ) {
			var ddOff = dd.offset();
			var list = $(dd.data(dataNames.list)).css( {"top" : (ddOff.top + dd.height() + setup.listDistance) +"px", "left" : ddOff.left +"px", "visibility" : "hidden"} ).show();
			
			if( list[0].offsetHeight > 250 ) {
				list.css({"height": "250px", "overflow-y": "scroll"});
				list.css("height", (250 - (list[0].offsetHeight - 250)) +"px");
			}
			
			list.css("visibility", "").hide();
		}
		
		function addDropdown( dd ) {
			if( !dropdowns ) {
				dropdowns = dd;
			} else {
				dropdowns = dropdowns.add(dd);
			}
		}
		
		return this.each( function() {
			buildDropdown( this );
		});
	}
	
	function debug( str ) {
		var dbg = $("#debug");
		
		if( dbg.length <= 0 )
			dbg = $('<div id="debug"></div>').appendTo("body");
		
		dbg.append("<p>"+ str +"</p>");
	}
})(jQuery);
