(function ($) {
    $.fn.sexyCombo = function (config) {
        return this.each(function () {
            if ("SELECT" != this.tagName.toUpperCase()) {
                return
            }
            new $sc(this, config)
        })
    };
    var defaults = {
        skin: "sexy",
        suffix: "__sexyCombo",
        hiddenSuffix: "__sexyComboHidden",
        initialHiddenValue: "",
        emptyText: "",
        autoFill: false,
        triggerSelected: true,
        filterFn: null,
        dropUp: false,
        separator: ",",
        showListCallback: null,
        hideListCallback: null,
        initCallback: null,
        initEventsCallback: null,
        changeCallback: null,
        textChangeCallback: null,
        flipListPos: true
    };
    $.sexyCombo = function (selectbox, config) {
        if (selectbox.tagName.toUpperCase() != "SELECT") return;
        this.config = $.extend({},
        defaults, config || {});
        this.selectbox = $(selectbox);
        this.options = this.selectbox.children().filter("option");
        this.wrapper = this.selectbox.wrap("<div>").hide().parent().addClass("combo").addClass(this.config.skin);
        this.input = $("<input type='text' />").appendTo(this.wrapper).attr("autocomplete", "off").attr("value", "").attr("name", this.selectbox.attr("name") + this.config.suffix);
        this.hidden = $("<input type='hidden' />").appendTo(this.wrapper).attr("autocomplete", "off").attr("value", this.config.initialHiddenValue).attr("name", this.selectbox.attr("name") + this.config.hiddenSuffix);
        this.icon = $("<div />").appendTo(this.wrapper).addClass("icon");
        this.listWrapper = $("<div />").appendTo(this.wrapper).addClass("list-wrapper");
        if ("function" == typeof this.listWrapper.bgiframe) {
            this.listWrapper.bgiframe({
                height: 1000
            })
        }
        this.updateDrop();
        this.list = $("<ul />").appendTo(this.listWrapper);
        var self = this;
        this.options.each(function () {
            var optionText = $.trim($(this).text());
            $("<li />").appendTo(self.list).html("<div>" + optionText + "</div>").addClass("visible"+($(this).attr('selected') ? ' active' : ''))
        });
        this.listItems = this.list.children();
        var optWidths = [];
        this.listItems.find("div").each(function () {
            optWidths.push($(this).outerWidth())
        });
        optWidths = optWidths.sort(function (a, b) {
            return a - b
        });
        var maxOptionWidth = optWidths[optWidths.length - 1];
        this.singleItemHeight = this.listItems.outerHeight();
        this.listWrapper.addClass("invisible");
        if ($.browser.opera) {
            this.wrapper.css({
                position: "relative",
                left: "0",
                top: "0"
            })
        }
        this.filterFn = ("function" == typeof(this.config.filterFn)) ? this.config.filterFn : this.filterFn;
        this.lastKey = null;
        this.multiple = this.selectbox.attr("multiple");
        var self = this;
        this.wrapper.data("sc:lastEvent", "click");
        this.overflowCSS = "overflowY";
        if (this.listWrapper.innerWidth() < maxOptionWidth) {
            this.overflowCSS = "overflow"
        }
        this.currentTextWithoutAutoFill = '';
        this.notify("init");
        this.initEvents()
    };
    var $sc = $.sexyCombo;
    $sc.fn = $sc.prototype = {};
    $sc.fn.extend = $sc.extend = $.extend;
    $sc.fn.extend({
        initEvents: function () {
            var self = this;
            this.icon.bind("click", function () {
                if (self.input.attr("disabled")) {
                    self.input.attr("disabled", false)
                }
                self.wrapper.data("sc:lastEvent", "click");
                self.filter();
                self.iconClick()
            });
            /*this.listItems.bind("mouseover", function (e) {
                self.highlight(e.target)
            });*/
            this.listItems.find("div").bind("mouseover", function (e) {
                self.highlight(e.target.parentNode);
            });
            this.listItems.bind("click", function (e) {
                self.listItemClick($(e.target))
            });
            this.input.bind("keyup", function (e) {
                self.wrapper.data("sc:lastEvent", "key")
            });
            this.input.bind("keyup", function (e) {
                self.keyUp(e)
            });
            this.input.bind("keypress", function (e) {
                if ($sc.KEY.RETURN == e.keyCode) {
                    e.preventDefault()
                }
                if ($sc.KEY.TAB == e.keyCode) e.preventDefault()
            });
            $(document).bind("click", function (e) {
                if ((self.icon.get(0) == e.target) || (self.input.get(0) == e.target)) return;
                self.hideList()
            });
            this.triggerSelected();
            this.applyEmptyText();
            this.input.bind("click", function (e) {
                self.wrapper.data("sc:lastEvent", "click");
                self.icon.trigger("click")
            });
            this.wrapper.bind("click", function () {
                self.wrapper.data("sc:lastEvent", "click")
            });
            this.input.bind("keydown", function (e) {
                if (9 == e.keyCode) {
                    e.preventDefault()
                }
            });
            this.wrapper.bind("keyup", function (e) {
                var k = e.keyCode;
                for (key in $sc.KEY) {
                    if ($sc.KEY[key] == k) {
                        return
                    }
                }
                self.wrapper.data("sc:lastEvent", "key")
            });
            this.input.bind("click", function () {
                self.wrapper.data("sc:lastEvent", "click")
            });
            this.icon.bind("click", function (e) {
                if (!self.wrapper.data("sc:positionY")) {
                    self.wrapper.data("sc:positionY", e.pageY)
                }
            });
            this.input.bind("click", function (e) {
                if (!self.wrapper.data("sc:positionY")) {
                    self.wrapper.data("sc:positionY", e.pageY)
                }
            });
            this.wrapper.bind("click", function (e) {
                if (!self.wrapper.data("sc:positionY")) {
                    self.wrapper.data("sc:positionY", e.pageY)
                }
            });
            this.notify("initEvents")
        },
        getTextValueWitoutAutoFill: function () {
            if (this.currentTextWithoutAutoFill != '') {
                return this.currentTextWithoutAutoFill
            } else {
                return this.getTextValue()
            }
        },
        getTextValue: function () {
            return this.__getValue("input")
        },
        getCurrentTextValue: function () {
            return this.__getCurrentValue("input")
        },
        getHiddenValue: function () {
            return this.__getValue("hidden")
        },
        getCurrentHiddenValue: function () {
            return this.__getCurrentValue("hidden")
        },
        __getValue: function (prop) {
            prop = this[prop];
            if (!this.multiple) return $.trim(prop.val());
            var tmpVals = prop.val().split(this.config.separator);
            var vals = [];
            for (var i = 0, len = tmpVals.length; i < len; ++i) {
                vals.push($.trim(tmpVals[i]))
            }
            vals = $sc.normalizeArray(vals);
            return vals
        },
        __getCurrentValue: function (prop) {
            prop = this[prop];
            if (!this.multiple) return $.trim(prop.val());
            return $.trim(prop.val().split(this.config.separator).pop())
        },
        iconClick: function () {
            if (this.listVisible()) {
                this.hideList();
                this.input.blur()
            } else {
                this.showList();
                this.input.focus();
                if (this.input.val().length) {
                    this.selection(this.input.get(0), 0, this.input.val().length)
                }
            }
        },
        listVisible: function () {
            return this.listWrapper.hasClass("visible")
        },
        showList: function () {
            if (!this.listItems.filter(".visible").length) return;
            this.listWrapper.removeClass("invisible").addClass("visible");
            this.wrapper.css("zIndex", "99999");
            this.listWrapper.css("zIndex", "99999");
            this.setListHeight();
            var listHeight = this.listWrapper.height();
            var inputHeight = this.wrapper.height();
            var bottomPos = parseInt(this.wrapper.data("sc:positionY")) + inputHeight + listHeight;
            var maxShown = $(window).height() + $(document).scrollTop();
            if (this.flipListPos && bottomPos > maxShown) {
                this.setDropUp(true)
            } else {
                this.setDropUp(false)
            }
            //this.highlightFirst();
            this.highlightCurrent();
            this.listWrapper.scrollTop(0);
            this.notify("showList")
        },
        hideList: function () {
            if (this.listWrapper.hasClass("invisible")) return;
            this.listWrapper.removeClass("visible").addClass("invisible");
            this.wrapper.css("zIndex", "0");
            this.listWrapper.css("zIndex", "99999");
            this.notify("hideList")
        },
        getListItemsHeight: function () {
            var itemHeight = this.singleItemHeight;
            return itemHeight * this.liLen()
        },
        setOverflow: function () {
            var maxHeight = this.getListMaxHeight();
            if (this.getListItemsHeight() > maxHeight) this.listWrapper.css(this.overflowCSS, "scroll");
            else this.listWrapper.css(this.overflowCSS, "hidden")
        },
        highlight: function (activeItem) {
            if (($sc.KEY.DOWN == this.lastKey) || ($sc.KEY.UP == this.lastKey)) return;
            this.listItems.removeClass("active");
            $(activeItem).addClass("active")
        },
        setComboValue: function (val, pop, hideList) {
            var oldVal = this.input.val();
            var v = "";
            if (this.multiple) {
                v = this.getTextValue();
                if (pop) v.pop();
                v.push($.trim(val));
                v = $sc.normalizeArray(v);
                v = v.join(this.config.separator) + this.config.separator
            } else {
                v = $.trim(val)
            }
            this.input.val(v);
            this.setHiddenValue(val);
            this.filter();
            if (hideList) this.hideList();
            this.input.removeClass("empty");
            if (this.multiple) this.input.focus();
            if (this.input.val() != oldVal || this.lastKey == 13) this.notify("textChange")
        },
        setHiddenValue: function (val) {
            var set = false;
            val = $.trim(val);
            var oldVal = this.hidden.val();
            if (!this.multiple) {
                for (var i = 0, len = this.options.length; i < len; ++i) {
                    if (val == this.options.eq(i).text()) {
                        this.hidden.val(this.options.eq(i).val());
                        set = true;
                        break
                    }
                }
            } else {
                var comboVals = this.getTextValue();
                var hiddenVals = [];
                for (var i = 0, len = comboVals.length; i < len; ++i) {
                    for (var j = 0, len1 = this.options.length; j < len1; ++j) {
                        if (comboVals[i] == this.options.eq(j).text()) {
                            hiddenVals.push(this.options.eq(j).val())
                        }
                    }
                }
                if (hiddenVals.length) {
                    set = true;
                    this.hidden.val(hiddenVals.join(this.config.separator))
                }
            }
            if (!set) {
                this.hidden.val(this.config.initialHiddenValue)
            }
            if (oldVal != this.hidden.val()) this.notify("change");
            this.selectbox.val(this.hidden.val());
            this.selectbox.trigger("change")
        },
        listItemClick: function (item) {
            this.setComboValue(item.text(), true, true);
            this.inputFocus()
        },
        filter: function () {
            if ("yes" == this.wrapper.data("sc:optionsChanged")) {
                var self = this;
                this.listItems.remove();
                this.options = this.selectbox.children().filter("option");
                this.options.each(function () {
                    var optionText = $.trim($(this).text());
                    $("<li />").appendTo(self.list).text(optionText).addClass("visible")
                });
                this.listItems = this.list.children();
                this.listItems.bind("mouseover", function (e) {
                    self.highlight(e.target)
                });
                this.listItems.bind("click", function (e) {
                    self.listItemClick($(e.target))
                });
                self.wrapper.data("sc:optionsChanged", "")
            }
            var comboValue = this.input.val();
            var self = this;
            this.listItems.each(function () {
                var $this = $(this);
                var itemValue = $this.text();
                if (self.filterFn.call(self, self.getCurrentTextValue(), itemValue, self.getTextValue())) {
                    $this.removeClass("invisible").addClass("visible")
                } else {
                    $this.removeClass("visible").addClass("invisible")
                }
            });
            this.setOverflow();
            this.setListHeight()
        },
        filterFn: function (currentComboValue, itemValue, allComboValues) {
            if ("click" == this.wrapper.data("sc:lastEvent")) {
                return true
            }
            if (!this.multiple) {
                return itemValue.toLowerCase().indexOf(currentComboValue.toLowerCase()) == 0
            } else {
                for (var i = 0, len = allComboValues.length; i < len; ++i) {
                    if (itemValue == allComboValues[i]) {
                        return false
                    }
                }
                return itemValue.toLowerCase().search(currentComboValue.toLowerCase()) == 0
            }
        },
        getListMaxHeight: function () {
            var result = parseInt(this.listWrapper.css("maxHeight"), 10);
            if (isNaN(result)) {
                result = 200
            }
            return result
        },
        setListHeight: function () {
            var liHeight = this.getListItemsHeight();
            var maxHeight = this.getListMaxHeight();
            var listHeight = this.listWrapper.height();
            if (liHeight < listHeight) {
                this.listWrapper.height(liHeight);
                return liHeight
            } else if (liHeight > listHeight) {
                this.listWrapper.height(Math.min(maxHeight, liHeight));
                return Math.min(maxHeight, liHeight)
            }
        },
        getActive: function () {
            return this.listItems.filter(".active")
        },
        keyUp: function (e) {
            this.lastKey = e.keyCode;
            var k = $sc.KEY;
            switch (e.keyCode) {
            case k.RETURN:
            case k.TAB:
                this.setComboValue(this.getActive().text(), true, true);
                if (!this.multiple) this.input.blur();
                break;
            case k.DOWN:
                this.highlightNext();
                break;
            case k.UP:
                this.highlightPrev();
                break;
            case k.ESC:
                this.hideList();
                break;
            default:
                this.inputChanged();
                break
            }
        },
        liLen: function () {
            return this.listItems.filter(".visible").length
        },
        inputChanged: function () {
            this.filter();
            if (this.liLen()) {
                this.showList();
                this.setOverflow();
                this.setListHeight();
            } else {
                this.hideList();
            }
            this.setHiddenValue(this.input.val());
            this.notify("textChange")
        },
        highlightFirst: function () {
            this.listItems.removeClass("active").filter(".visible:eq(0)").addClass("active");
            this.listWrapper.animate({scrollTop: 0}, 100);
            this.autoFill();
        },
        highlightCurrent: function () {
            var active = this.getActive();
            if (active.length) {
                if((active.position().top < this.listWrapper.scrollTop()) || (active.position().top > (this.listWrapper.scrollTop() + this.listWrapper.height())))
                    this.listWrapper.animate({scrollTop: (this.listWrapper.scrollTop() + active.position().top)}, 100);
                this.autoFill();
            } else {
                this.highlightFirst();
            }
        },
        highlightNext: function () {
            var $next = this.getActive().next();
            while ($next.hasClass("invisible") && $next.length) {
                $next = $next.next()
            }
            if ($next.length) {
                this.listItems.removeClass("active");
                $next.addClass("active");
                this.scrollDown()
            }
        },
        scrollDown: function () {
            if ("scroll" != this.listWrapper.css(this.overflowCSS)) return;
            var beforeActive = this.getActiveIndex() + 1;
            var minScroll = this.listItems.outerHeight() * beforeActive - this.listWrapper.height();
            if ($.browser.msie) minScroll += beforeActive;
            if (this.listWrapper.scrollTop() < minScroll) this.listWrapper.scrollTop(minScroll)
        },
        highlightPrev: function () {
            var $prev = this.getActive().prev();
            while ($prev.length && $prev.hasClass("invisible")) $prev = $prev.prev();
            if ($prev.length) {
                this.getActive().removeClass("active");
                $prev.addClass("active");
                this.scrollUp()
            }
        },
        getActiveIndex: function () {
            return $.inArray(this.getActive().get(0), this.listItems.filter(".visible").get())
        },
        scrollUp: function () {
            if ("scroll" != this.listWrapper.css(this.overflowCSS)) return;
            var maxScroll = this.getActiveIndex() * this.listItems.outerHeight();
            if (this.listWrapper.scrollTop() > maxScroll) {
                this.listWrapper.scrollTop(maxScroll)
            }
        },
        applyEmptyText: function () {
            if (!this.config.emptyText.length) return;
            var self = this;
            this.input.bind("focus", function () {
                self.inputFocus()
            }).bind("blur", function () {
                self.inputBlur()
            });
            if ("" == this.input.val()) {
                this.input.addClass("empty").val(this.config.emptyText)
            }
        },
        inputFocus: function () {
            if (this.input.hasClass("empty")) {
                this.input.removeClass("empty").val("")
            }
        },
        inputBlur: function () {
            if ("" == this.input.val()) {
                this.input.addClass("empty").val(this.config.emptyText)
            }
        },
        triggerSelected: function () {
            if (!this.config.triggerSelected) return;
            var self = this;
            var set = false;
            this.options.each(function () {
                if ($(this).attr("selected")) {
                    set = true;
                    self.setComboValue($(this).text(), false, true)
                }
            })
        },
        autoFill: function () {
            if (!this.config.autoFill || ($sc.KEY.BACKSPACE == this.lastKey) || ($sc.KEY.DEL == this.lastKey) || ($sc.KEY.LEFT == this.lastKey) || ($sc.KEY.RIGHT == this.lastKey) || ($sc.KEY.HOME == this.lastKey) || ($sc.KEY.END == this.lastKey) || ($sc.KEY.SHIFT == this.lastKey) || this.multiple) return;
            var curVal = this.input.val();
            var newVal = this.getActive().text();
            this.currentTextWithoutAutoFill = '';
            if (newVal.toLowerCase().search(curVal.toLowerCase()) == 0) {
                if (curVal != newVal) this.currentTextWithoutAutoFill = curVal;
                this.input.val(newVal);
                this.selection(this.input.get(0), curVal.length, newVal.length)
            }
        },
        selection: function (field, start, end) {
            if (field.createTextRange) {
                var selRange = field.createTextRange();
                selRange.collapse(true);
                selRange.moveStart("character", start);
                selRange.moveEnd("character", end);
                selRange.select()
            } else if (field.setSelectionRange) {
                field.setSelectionRange(start, end)
            } else {
                if (field.selectionStart) {
                    field.selectionStart = start;
                    field.selectionEnd = end
                }
            }
        },
        updateDrop: function () {
            if (this.config.dropUp) this.listWrapper.addClass("list-wrapper-up");
            else this.listWrapper.removeClass("list-wrapper-up")
        },
        setDropUp: function (drop) {
            this.config.dropUp = drop;
            this.updateDrop()
        },
        notify: function (evt) {
            if (!$.isFunction(this.config[evt + "Callback"])) return;
            this.config[evt + "Callback"].call(this)
        }
    });
    $sc.extend({
        KEY: {
            UP: 38,
            DOWN: 40,
            LEFT: 37,
            RIGHT: 39,
            DEL: 46,
            TAB: 9,
            RETURN: 13,
            ESC: 27,
            COMMA: 188,
            PAGEUP: 33,
            PAGEDOWN: 34,
            BACKSPACE: 8,
            HOME: 36,
            END: 35,
            SHIFT: 16
        },
        log: function (msg) {
            var $log = $("#log");
            $log.html($log.html() + msg + "<br />")
        },
        createSelectbox: function (config) {
            var $selectbox = $("<select />").appendTo(config.container).attr({
                name: config.name,
                id: config.id,
                size: "1"
            });
            if (config.multiple) $selectbox.attr("multiple", true);
            var data = config.data;
            var selected = false;
            for (var i = 0, len = data.length; i < len; ++i) {
                selected = data[i].selected || false;
                $("<option />").appendTo($selectbox).attr("value", data[i].value).text(data[i].text).attr("selected", selected)
            }
            return $selectbox.get(0)
        },
        create: function (config) {
            var defaults = {
                name: "",
                id: "",
                data: [],
                multiple: false,
                container: $(document),
                url: "",
                ajaxData: {}
            };
            config = $.extend({},
            defaults, config || {});
            if (config.url) {
                return $.getJSON(config.url, config.ajaxData, function (data) {
                    delete config.url;
                    delete config.ajaxData;
                    config.data = data;
                    return $sc.create(config)
                })
            }
            config.container = $(config.container);
            var selectbox = $sc.createSelectbox(config);
            return new $sc(selectbox, config)
        },
        deactivate: function ($select) {
            $select = $($select);
            $select.each(function () {
                if ("SELECT" != this.tagName.toUpperCase()) {
                    return
                }
                var $this = $(this);
                if (!$this.parent().is(".combo")) {
                    return
                }
            })
        },
        activate: function ($select) {
            $select = $($select);
            $select.each(function () {
                if ("SELECT" != this.tagName.toUpperCase()) {
                    return
                }
                var $this = $(this);
                if (!$this.parent().is(".combo")) {
                    return
                }
                $this.parent().find("input[type='text']").attr("disabled", false)
            })
        },
        changeOptions: function ($select) {
            $select = $($select);
            $select.each(function () {
                if ("SELECT" != this.tagName.toUpperCase()) {
                    return
                }
                var $this = $(this);
                var $wrapper = $this.parent();
                var $input = $wrapper.find("input[type='text']");
                var $listWrapper = $wrapper.find("ul").parent();
                $listWrapper.removeClass("visible").addClass("invisible");
                $wrapper.css("zIndex", "0");
                $listWrapper.css("zIndex", "99999");
                $input.val("");
                $wrapper.data("sc:optionsChanged", "yes");
                var $selectbox = $this;
                $selectbox.parent().find("input[type='text']").val($selectbox.find("option:eq(0)").text());
                $selectbox.parent().data("sc:lastEvent", "click");
                $selectbox.find("option:eq(0)").attr('selected', 'selected')
            })
        },
        normalizeArray: function (arr) {
            var result = [];
            for (var i = 0, len = arr.length; i < len; ++i) {
                if ("" == arr[i]) continue;
                result.push(arr[i])
            }
            return result
        }
    })
})(jQuery);