(function($) {
    var valueDataKey = "StyledSelectValue";
    var contextStorageKey = "StyledSelectOptions";
    
    function StyledSelectContext($containerHandle, $optionBoxHandle, opts) {
        this.$Container = $containerHandle;
        this.$OptionBox = $optionBoxHandle;
        this.Opts = opts;
    }
    
    $.fn.extend({
        styledselect: function(opts) {
            var $select, selectEl, containerID, $container, optionBoxID, $optionBox, opt, containerOffset,
                isOpen = false,
                optionArr = [],
                defaults = {
                    optionBoxCssClass: "StyledSelectOptionBox",
                    optionCssClass: "StyledSelectOption",
                    optionHoverCssClass: "StyledSelectOptionHover",
                    optionFocussedCssClass: "StyledSelectOptionFocus",
                    containerCssClass: "StyledSelectContainer",
                    inputProxyCssClass: "StyledSelectInputProxy"
                };
            opts = $.extend(defaults, opts);

            if (this.length == 0 || this.get(0).tagName.toLowerCase() != "select") {
                return this;
            }


            selectEl = this[0];
            $select = $(selectEl);
            
            // Get collection of options
            $("option", $select).each(function(i) {
                optionArr[i] = {
                    Value: $(this).val(),
                    Text: ($(this).text()) ? $(this).text() : "&nbsp;"
                };
            });



            var value = $(this).val();
            var displayValue = (optionArr.length > 0) ? optionArr[0].Text : "";
            for (var i = 0; i < optionArr.length; i++) {
                if (optionArr[i].Value == value) {
                    displayValue = optionArr[i].Text;
                    break;
                }
            }



            // Add container
            containerID = selectEl.id + "Container";
            $select.before("<div id=\"" + containerID + "\" class=\"" + selectEl.className + " " + opts.containerCssClass + "\">" + displayValue + "</div>");
            $container = $("#" + containerID);

            // Add option box
            optionBoxID = selectEl.id + "OptionBox";
            $("body").append("<div id=\"" + optionBoxID + "\" class=\"" + opts.optionBoxCssClass + "\"></div>");
            $optionBox = $("#" + optionBoxID);
            $optionBox.css({
                position: "absolute",
                width: $container.width() + 2,
                display: "none"
            });

            // Fill option box
            for (var i = 0; i < optionArr.length; i++) {
                $optionBox.append("<a class=\"" + opts.optionCssClass + "\"></a>");
                opt = $("a:last", $optionBox);
                opt.html(optionArr[i].Text);
                opt.data(valueDataKey, optionArr[i].Value);
                opt.click(optionClicked);
                opt.hover(function() { 
                    $optionBox.find("a").removeClass(opts.optionHoverCssClass); 
                    $(this).addClass(opts.optionHoverCssClass) 
                }, function() { 
                    $(this).removeClass(opts.optionHoverCssClass) 
                });
            }
           
            // Create textbox to act as a proxy for focus behaviour
            var focusProxyID = selectEl.id + "FocusProxy";
            $select.before("<input type='text' id='" + focusProxyID + "' class='" + opts.inputProxyCssClass + "' style='width: 0px; height: 0px; border: 1px solid #fff; font-size: 0px; line-height: 0px; margin: 0px; padding: 0px; color: #fff; float: left;'></input>");
            var $focusProxy = $("#" + focusProxyID);
            
            // Bind events
            $container.bind("click", function() {
                $focusProxy.get(0).focus();
                openOptionBox();
            });
            
            var closeTimeout;

            $optionBox.bind("focus", function(e) {
                clearTimeout(closeTimeout);
                $focusProxy.get(0).focus();
            });
            bindFocusEventSet();
            $focusProxy.bind("focus", function(e) {
                clearTimeout(closeTimeout);
                $container.addClass(opts.optionFocussedCssClass);
            });
            $focusProxy.bind("blur", function(e) {
                $container.removeClass(opts.optionFocussedCssClass);
                closeTimeout = setTimeout(function () {
                    if (isOpen) {
                        closeOptionBox();
                    }
                }, 300);
            });
            
            // Store context for future use
            $select.data(contextStorageKey, new StyledSelectContext($container, $optionBox, opts));

            // Hide original
            $select.hide();
            
            // Return a jQuery object
            return this;


            function optionClicked(e) {
                $container.text($(this).text());
                $select.val($(this).data(valueDataKey));
                if ($select.change != undefined) {
                    try {
                    	$select.change();
                    } catch (err) {
                    
                    }
                }
                closeOptionBox();
                e.stopPropagation();
            }
            
            function highlightCurrentOption() {
                var currText = $container.text(),
                    $options = $("a", $optionBox),
                    highlightClass = opts.optionHoverCssClass;
                    
                if ($options.length > 0) {
                    $options.removeClass(highlightClass);
                    for (var i = 0; i < $options.length; i++) {
                        if ($options.eq(i).text() == currText) {
                            $options.eq(i).addClass(highlightClass);
                            return;
                        }
                    }
                    $options.eq(0).addClass(highlightClass);
                }
            }
            
            function moveToNextOption() {
                var $options = $("a", $optionBox),
                    highlightClass = opts.optionHoverCssClass,
                    $currOpt = $options.filter("." + highlightClass + ":first"),
                    index = $options.index($currOpt),
                    newIndex = index + 1,
                    $newOpt;

                if (newIndex < $options.length) {
                    $newOpt = $options.eq(newIndex);
                    $currOpt.removeClass(highlightClass);
                    $newOpt.addClass(highlightClass);
                    
                    scrollToOption($newOpt, newIndex);
                }
                
            }
            
            function moveToPrevOption() {
                var $options = $("a", $optionBox),
                    highlightClass = opts.optionHoverCssClass,
                    $currOpt = $options.filter("." + highlightClass + ":first"),
                    index = $options.index($currOpt),
                    newIndex = index - 1,
                    $newOpt;

                if (newIndex >= 0) {
                    $newOpt = $options.eq(newIndex);
                    $currOpt.removeClass(highlightClass);
                    $newOpt.addClass(highlightClass);
                    
                    scrollToOption($newOpt, newIndex);                    
                }
                
            }
            
            function scrollToOption($option, index) {
                var $parent = $option.parent(),
                    pHeight = $parent.height(),
                    pScrollTop = $parent.scrollTop(),
                    optHeight = $option.height(),
                    optOffset = optHeight * index;
                
                if (optOffset < pScrollTop) {
                    $parent.scrollTop(optOffset);
                } else if (optOffset + optHeight > pScrollTop + pHeight) {
                    $parent.scrollTop(pScrollTop + optHeight);
                }
            }
            
            function selectCurrentOption() {
                var $options = $("a", $optionBox),
                    highlightClass = opts.optionHoverCssClass,
                    $currOpt = $options.filter("." + highlightClass + ":first");
                
                if ($currOpt) {
                    $currOpt.triggerHandler("click");
                }
            }

            function openOptionBox(e) {
                var containerOffset = $container.offset();
                $optionBox.css({
                    top: containerOffset.top + $container.outerHeight(),
                    left: containerOffset.left
                });
                $optionBox.show();
                highlightCurrentOption();
                isOpen = true;
                if (e) e.stopPropagation();
            }

            function closeOptionBox(e) {
                $optionBox.hide();
                isOpen = false;
                if (e) e.stopPropagation();
            }
            
            function bindFocusEventSet() {
                $focusProxy.bind("keydown.focusEventSet", function(e) {
                    var keynum;
                    if (!e) {
                        e = window.event;
                    }
                    keynum = e.keyCode ? e.keyCode : e.which;

                    if (keynum == 40) {
                        //DOWN ARROW pressed
                        if (isOpen) {
                            moveToNextOption();    
                        } else {
                            openOptionBox();
                        }
                        return false;
                    } else if (keynum == 38) {
                        //UP ARROW pressed
                        if (isOpen) {
                            moveToPrevOption();
                            return false;
                        }
                    } else if (keynum == 9) {
                        //TAB ARROW pressed
                        if (isOpen) {
                            selectCurrentOption();
                        }
                    } else if (keynum == 13 || keynum == 32) {
                        // ENTER or SPACEBAR pressed
                        if (isOpen) {
                            selectCurrentOption();
                            return false;
                        }
                    }
                });
            }
            
            function unbindFocusEventSet() {
                $focusProxy.unbind("keydown.focusEventSet");
            }
        },
        
        refreshStyledOptions: function() {
            if (this.length == 0 || this.get(0).tagName.toLowerCase() != "select") {
                return this;
            }

            var select = this[0],
                $select = $(select),
                context = $select.data(contextStorageKey),
                optionArr = [],
                valueObsolete = true,
                opts, $container, $optionBox, currentValue;
            if (context) {
                opts = context.Opts;
                $container = context.$Container;
                $optionBox = context.$OptionBox;
                
                currentValue = $container.html();
                
                // Get collection of options
                $("option", $select).each(function(i) {
                    optionArr[i] = {
                        Value: $(this).val(),
                        Text: ($(this).text()) ? $(this).text() : "&nbsp;"
                    };
                });

                $optionBox.empty();
                for (var i = 0; i < optionArr.length; i++) {
                    if (optionArr[i].Text == currentValue) {
                        valueObsolete = false;
                    }
                    $optionBox.append("<a class=\"" + opts.optionCssClass + "\"></a>");
                    opt = $("a:last", $optionBox);
                    opt.html(optionArr[i].Text);
                    opt.data(valueDataKey, optionArr[i].Value);
                    opt.click(optionClicked);
                    opt.hover(function() { $(this).addClass(opts.optionHoverCssClass) }, function() { $(this).removeClass(opts.optionHoverCssClass) });
                }
                
                // Check if selected option has now been removed
                if (valueObsolete) {
                    $container.html("");
                }
            }
            
            return this;
            
            function optionClicked(e) {
                $container.text($(this).text());
                $select.val($(this).data(valueDataKey));
                if ($select.change != undefined) {
                    try {
	                    $select.change();
	                } catch (err) {
	                
	                }
                }
                closeOptionBox();
                e.stopPropagation();
            }

            function closeOptionBox(e) {
                $optionBox.hide();
                if (e) e.stopPropagation();
            }
                
        }
    });
})(jQuery);
