﻿(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 ul = $(d.createElement("ul")).addClass(setup.listBaseClassName).addClass(setup.listClassName);

                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(ul);
                    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, $("ul", 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);
            positionList(dd);
            tweenDropdown(dd, false);
            dd.data(dataNames.closed, false);
        }

        function closeDropdown(dd) {
            var dd = (dd) ? $(dd) : $(this);
            tweenDropdown(dd, true);
            dd.data(dataNames.closed, true);
        }

        function tweenDropdown(dd, hide) {
            var list = dd.data(dataNames.list);
            var tween = getTween(hide);

            eval("list." + tween.name + "(" + tween.speed + ")");
        }

        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();
            $(dd.data(dataNames.list)).css({ "top": (ddOff.top + dd.height() + setup.listDistance) + "px", "left": ddOff.left + "px" });
        }

        function addDropdown(dd) {
            if (!dropdowns) {
                dropdowns = dd;
            } else {
                dropdowns = dropdowns.add(dd);
            }
        }

        return this.each(function() {
            buildDropdown(this);
        });
    }
})(jQuery);
