/***************************************************************************

    slick-combo 1.0.0

 ***************************************************************************/
(function($) {
	$.fn.swallowEvent = function(event) {
		return this.each(function() {
			$(this).bind(event, function(){return false;})
		});
	};

	$.fn.unselectable = function() {
		return this.each(function() {
			$(this).swallowEvent("selectstart")
				.css("-moz-user-select", "none")
				.css("-khtml-user-select", "none")
				.attr("unselectable", "on");
		});
	};
	
    $.fn.slickCombo = function(config) {
        return this.each(function() {
            if ("SELECT" != this.tagName.toUpperCase()) {
                return;
            }
            $(this).data("slickCombo", new $sc(this, config));
        });
    };

    var defaults = {
        skin: "slick",
        suffix: "_sCombo",

		actionUrl: "",
		postData: null,

        postValue: false,
        valueName: "",

        postText: false,
        textName: "",

        //if provided, will be the value of the text input when it has no value and focus
        emptyText: "",

        //if true, selected option of the selectbox will be the initial value of the combo
        triggerSelected: true,

        //if true, the options list will be placed above text input
        dropUp: false,

        //key json name for key/value pair
        key: "value",

        //value json for key/value pair
        value: "text",

        //all callback functions are called in the scope of the current slickCombo instance

        //called after dropdown list appears
        showListCallback: null,

        //called after dropdown list disappears
        hideListCallback: null,

        //called at the end of constructor
        initCallback: null,

        //called at the end of initEvents function
        initEventsCallback: null,

        //called when both text and hidden inputs values are changed
        changeCallback: null,

        //called when text input's value is changed
        textChangeCallback: null,

		sourceUpdateCallback: null,

        checkWidth: true,

        isActive: true,

        source: null
    };

    //constructor
    //creates initial markup and does some initialization
    $.slickCombo = function(selectbox, config) {
        if (selectbox.tagName.toUpperCase() != "SELECT")
            return;

        this.config = $.extend({}, defaults, config || {});

        this.selectbox = $(selectbox);
        this.options = this.selectbox.children("option");

        this.wrapper = this.selectbox.wrap("<div>")
            .hide()
            .parent()
            .addClass("combo")
            .addClass(this.config.skin);

		this.hidden = $("<input type='hidden' />")
            .appendTo(this.wrapper)
            .attr("value", "");

		if (this.selectbox.attr("id"))
			this.hidden.attr("id", this.selectbox.attr("id") + this.config.suffix + "h");

        this.input = $("<input type='text' />")
            .appendTo(this.wrapper)
            .attr("autocomplete", "off")
            .attr("readonly", true)
			.unselectable()
            .attr("value", "");

		if (this.selectbox.attr("id"))
			this.input.attr("id", this.selectbox.attr("id") + this.config.suffix);

        this.icon = $("<div />")
            .appendTo(this.wrapper)
            .addClass("icon");

        this.listWrapper = $("<div />")
            .appendTo(this.wrapper)
            .addClass("list-wrapper");

        if (this.config.postText)
            this.input.attr("name", this.config.textName || this.selectbox.attr("name") + this.config.suffix);

        if(this.config.postValue) {
			 this.hidden.attr("name", this.config.valueName || this.selectbox.attr("name"));
			 this.selectbox.removeAttr("name");
        }
        else
            this.selectbox.removeAttr("name");

        if(!this.config.isActive)
            this.disable();
        else
            this.isDisabled = false;

        this.list = $("<ul />").appendTo(this.listWrapper);

        var self = this,
			optionText,
			thisOption,
			maxOptionWidth = 0,
			currentWidth,
			i;

        this.options.each(function() {
            optionText = $.trim($(this).text());
            thisOption = $("<li />")
                .appendTo(self.list)
                .html("<span>" + optionText + "</span>")
                .addClass("visible")
				.unselectable();

            if (self.config.checkWidth) {
				currentWidth = thisOption.find("span").outerWidth();
				if (maxOptionWidth < currentWidth)
					maxOptionWidth = currentWidth;
			}
        });
        this.listItems = this.list.children();

        if (this.config.checkWidth) {
            if (this.listWrapper.innerWidth() < maxOptionWidth) {
                this.listWrapper.css("width", "auto");
            }
			else {
				this.listWrapper.css("width", "100%");
			}
        }

        //if (this.config.source) this.source = $(this.config.source);

        this.singleItemHeight = this.listItems.outerHeight();
        this.listWrapper.addClass("invisible");

        if ($.browser.opera)
            this.wrapper.css({position: "relative",left: "0",top: "0"});

        this.lastKey = null;
        this.wrapper.data("sc:lastEvent", "click");
        this.overflowCSS = "overflowY";
        this.setOverflow();

        this.notify("init");
        this.initEvents();
    };

    //shortcuts
    var $sc = $.slickCombo;
    $sc.fn = $sc.prototype = {};
    $sc.fn.extend = $sc.extend = $.extend;

    $sc.fn.extend({
        //TOC of our plugin
        //initializes all event listeners
        initEvents: function() {
            var self = this;

            $(document).bind("click", function(e) {
                if (self.isDisabled) return;

                if ((self.icon.get(0) == e.target) || (self.input.get(0) == e.target))
                    return;

                self.hideList();
            });

            this.icon.bind("mousedown", function(e) {
                if (self.isDisabled) return;

                if (!self.wrapper.data("sc:positionY"))
                    self.wrapper.data("sc:positionY", e.pageY);

                self.iconClick();
            });

            this.input.bind("mousedown", function(e) {
                if (self.isDisabled) return;

                if (!self.wrapper.data("sc:positionY"))
                    self.wrapper.data("sc:positionY", e.pageY);

                self.iconClick();
            });

            this.input.bind("keypress", function(e) {
                if (self.isDisabled) return;

                e.keyCode = e.keyCode || e.which;
                if ($sc.KEY.RETURN == e.keyCode || $sc.KEY.TAB == e.keyCode)
                    e.preventDefault();

                self.keyPress(e);
            });

            this.listItems.bind("mouseover", function(e) {
                if ("LI" == e.target.nodeName.toUpperCase()) {
                    self.highlight(e.target);
                }
                else {
                    self.highlight($(e.target).parent());
                }
            });

            this.listItems.bind("mouseup", function(e) {
                self.listItemClick($(e.target));
            });

            if (this.config.source)
                this.config.source.selectbox.change(
                    function() {
                        self.processSourceUpdate();
                    }
                );

            this.notify("initEvents");

            this.triggerSelected();
            this.applyEmptyText();

        },

        processSourceUpdate: function () {
            var self = this;
            if (this.config.source.getValue() != "") {
				self.setComboValue("", true, true);
                $.ajax({
                    url: self.config.actionUrl,
                    dataType: 'json',
					data: ((typeof self.config.postData == "function") ? self.config.postData() : self.config.postData) || self.config.source.selectbox.serialize(),
                    success: function(data){
						self.returnData = data;
                        self.updateList(data.data);
                        self.enable();
                    },
                    error: function(theRequest, textStatus, errorThrown){
                        alert("We are sorry, an error occured while trying to retrieve your vehicle information.\nPlease try again.");
                    }
                });
            }
            else {
                self.clearAndDeactivate();
            }
        },

        getText: function() {
            return this.input.val() == this.config.emptyText ? "" : this.input.val();
        },

        getValue: function() {
            return this.input.val() == this.config.emptyText ? "" : this.hidden.val();
        },

        iconClick: function() {
            if (this.listVisible()) {
                this.hideList();
                this.input.blur();
            }
            else {
                this.showList();
				this.input.blur();                
            }
        },

        //returns true when dropdown list is visible
        listVisible: function() {
            return !this.listWrapper.hasClass("invisible");
        },

        //shows dropdown list
        showList: function() {
            if (!this.listItems.hasClass("visible"))
                return;

            this.listWrapper.removeClass("invisible").addClass("visible");
            this.wrapper.css("zIndex", "99999");
            this.listWrapper.css("zIndex", "99999");
            this.setListHeight();

            this.listItems = this.list.children();
            this.singleItemHeight = this.listItems.outerHeight();

            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 (bottomPos > maxShown)
                this.setDropUp(true);
            else
                this.setDropUp(false);

            if ("" == $.trim(this.input.val())) {
                this.highlightFirst();
                this.listWrapper.scrollTop(0);
            }
            else
                this.highlightSelected();

            this.notify("showList");
        },

        //hides dropdown list
        hideList: function() {
            if (this.listWrapper.hasClass("invisible"))
                return;

            this.listWrapper.removeClass("visible").
                addClass("invisible");
            this.wrapper.css("zIndex", "0");
            this.listWrapper.css("zIndex", "99999");
            if ("" == this.input.val()) {
                this.input.addClass("empty").
                val(this.config.emptyText);
            }
            this.notify("hideList");
        },

        //returns sum of all visible items height
        getListItemsHeight: function() {
            return this.singleItemHeight * this.liLen();
        },

        //changes list wrapper's overflow from hidden to scroll and vice versa (depending on list items height))
        setOverflow: function() {
            if (this.getListItemsHeight() > this.getListMaxHeight())
                this.listWrapper.css(this.overflowCSS, "scroll");
            else
                this.listWrapper.css(this.overflowCSS, "hidden");
        },

        //highlights active item of the dropdown list
        highlight: function(activeItem) {
            if (($sc.KEY.DOWN == this.lastKey) || ($sc.KEY.UP == this.lastKey))
                return;

            this.listItems.removeClass("active");
            $(activeItem).addClass("active");
        },

        enable: function () {
            this.wrapper.removeClass("input-disabled");
            this.input.removeClass("input-disabled");
            this.input.attr("disabled", null);
            this.isDisabled = false;
        },

        disable: function () {
            this.wrapper.addClass("input-disabled");
            this.input.addClass("input-disabled");
            this.input.attr("disabled", true);
            this.isDisabled = true;
        },

        clearAndDeactivate: function(){
            if (!this.isDisabled) {
                this.disable();
                this.wrapper.data("sc:optionsChanged", "yes");
                this.setComboValue("", true, false);
				this.notify("change");
				this.options.remove();
                this.selectbox.trigger("change");
            }
        },

        updateList: function(data) {
            var i,
				length = data.length,
				selected;

			this.options.remove();

            for (i = 0; i < length; i++) {
                selected = data[i].selected || false;
                $("<option />").appendTo(this.selectbox).
					attr("value", data[i][this.config.key]).
					text(data[i][this.config.value]).
					attr("selected", selected);
            }
            this.options = this.selectbox.children().filter("option");

            this.wrapper.data("sc:optionsChanged", "yes");
            this.refreshListItems();

            if (this.config.emptyText != this.input.val())
                this.setComboValue("", true, false);

            if(this.input.attr("disabled")) {
                this.wrapper.removeClass("input-disabled");
                this.input.attr("disabled", null);
            }
        },

        //sets text and hidden inputs value
        setComboValue: function(val, hideList, notify) {
            val = $.trim(val);

            if (this.input.val() != val) {

                var set = false,
                    i = 0,
                    length = this.options.length,
                    option = null;

                if ("" == val && this.config.emptyText != "")
                    this.input.val(this.config.emptyText)
                        .addClass("empty");
                else
                    this.input.val(val)
                        .removeClass("empty");

                for (i = 0; i < length; i++){
                    option = this.options.eq(i);
                    if (val == option.text()) {
                        this.selectbox.val(option.val());
						this.hidden.val(option.val());
                        set = true;
                        break;
                    }
                }
                if (!set) {
					this.selectbox.val("");
					this.hidden.val("");
				}

                if (hideList) this.hideList();
                if (notify && !this.input.attr("disabled")) {
                    this.notify("change");
                    this.selectbox.trigger("change");
                }

            }
        },

        setSelectedValue: function(val, hideList, notify) {
            val = $.trim(val);

            if (val != this.getValue()) {
                var set = false,
                    i = 0,
                    length = this.options.length,
                    option = null,
                    setText = "";

                for (i = 0 ; i < length; i++){
                    option = this.options.eq(i);
                    if (val == option.val()) {
                        this.selectbox.val(val);
						this.hidden.val(val);
                        setText = option.text();
                        set = true;
                        break;
                    }
                }

                if (set)
                    this.input.val(setText).removeClass("empty");
				else
					this.input.val(this.config.emptyText).addClass("empty");

                if (hideList) this.hideList();
                if (notify && !this.input.attr("disabled")) {
                    this.notify("change");
                    this.selectbox.trigger("change");
                }
            }
        },


        listItemClick: function(item) {
            this.setComboValue(item.text(), true, true);
        },

        //Refreshes the list from the options in the select box.
        refreshListItems: function() {
            if ("yes" == this.wrapper.data("sc:optionsChanged")) {
                var self = this,
					optionText,
					thisOption,
					maxOptionWidth = 0,
					currentWidth;

                this.listItems.remove();
                this.options = this.selectbox.children().filter("option");
                this.listWrapper.removeClass("invisible")
                this.listWrapper.css("width", "auto");

				this.options.each(function() {
					optionText = $.trim($(this).text());
					thisOption = $("<li />")
						.appendTo(self.list)
						.html("<span>" + optionText + "</span>")
						.addClass("visible");

					if (self.config.checkWidth) {
						currentWidth = thisOption.find("span").outerWidth();
						if (maxOptionWidth < currentWidth)
							maxOptionWidth = currentWidth;
					}
				});

                this.listItems = this.list.children();
                this.singleItemHeight = this.listItems.outerHeight();

                if ((this.config.checkWidth) && (this.input.innerWidth() > maxOptionWidth)) {
                    this.listWrapper.css("width", "100%");
                }

                this.listWrapper.addClass("invisible");

                this.listItems.bind("mouseover", function(e) {
                    if ("LI" == e.target.nodeName.toUpperCase()) {
                        self.highlight(e.target);
                    }
                    else {
                        self.highlight($(e.target).parent());
                    }
                });

                this.listItems.bind("mouseup", function(e) {
                    self.listItemClick($(e.target));
                });

                self.wrapper.data("sc:optionsChanged", "");
            }

            this.setOverflow();
            this.setListHeight();
        },

        //just returns integer value of list wrapper's max-height property
        getListMaxHeight: function() {
            var result = parseInt(this.listWrapper.css("maxHeight"), 10);
            if (isNaN(result)) {
                result = this.singleItemHeight * 10;
            }

            return result;
        },

        //corrects list wrapper's height depending on list items height
        setListHeight: function() {
            var liHeight = this.getListItemsHeight(),
				maxHeight = this.getListMaxHeight(),
				listHeight = this.listWrapper.height();
				
            if (liHeight < listHeight) {
                this.listWrapper.height(liHeight);
                return;
            }
            else if (liHeight > listHeight) {
                this.listWrapper.height(Math.min(maxHeight, liHeight));
                return;
            }
        },

        //returns active (hovered) element of the dropdown list
        getActive: function() {
            return this.listItems.filter(".active");
        },

        keyPress: function(e) {
            this.lastKey = e.keyCode;
            var k = $sc.KEY;
            switch (e.keyCode) {
                case k.RETURN:
					this.setComboValue(this.getActive().text(), true, true);
                    break;
                case k.TAB:
                    this.setComboValue(this.getActive().text(), true, true);
                    break;
                case k.DOWN:
                    this.highlightNext();
                    break;
                case k.UP:
                    this.highlightPrev();
                    break;
                case k.ESC:
                    this.hideList();
                    break;
                default:
                    this.inputChanged();
                    break;
            }
        },

        //returns number of currently visible list items
        liLen: function() {
            return this.listItems.filter(".visible").length;
        },

        //triggered when the user changes combo value by typing
        inputChanged: function() {
            var self = this,
				c = String.fromCharCode(this.lastKey),
				inputText = $.trim(this.input.val()),
				firstFound = false,
				currentFound = false,
				selectText = "";
            
            this.options.each(function() {
                var optionText = $.trim($(this).text());
                if (optionText.charAt(0).toUpperCase() == c.toUpperCase()) {
                    if (!firstFound) {
                        firstFound = true;
                        selectText = optionText;
                    }
					
                    if (optionText == inputText)
                        currentFound = true;
                    else if (firstFound && currentFound) {
                        selectText = optionText;
                        return false;
                    }
                }
            });

            if (selectText != "" && selectText != inputText) {
                self.setComboValue(selectText, false, true);
                self.highlightSelected();
            }
        },

        //highlights first item of the dropdown list
        highlightFirst: function() {
            this.listItems.removeClass("active").filter(".visible:eq(0)").addClass("active");
        },

        highlightSelected: function() {
            this.listItems.removeClass("active");
            var self = this,
				$this,
				val = $.trim(this.input.val()),
				found = false;

			this.listItems.each(function() {
				$this = $(this);
				if ($this.text() == val) {
					$this.addClass("active");
					self.listWrapper.scrollTop(0);
					self.scrollDown();
					found = true;
					return false;
				}
			});
			if (!found)
				this.highlightFirst();            
        },

        //highlights item of the dropdown list next to the currently active item
        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();
            }
        },

        //scrolls list wrapper down when needed
        scrollDown: function() {
            if ("scroll" != this.listWrapper.css(this.overflowCSS))
            return;

            var beforeActive = this.getActiveIndex() + 1,
				minScroll = this.listItems.outerHeight() * beforeActive - this.listWrapper.height();

            if ($.browser.msie)
                minScroll += beforeActive;

            if (this.listWrapper.scrollTop() < minScroll)
                this.listWrapper.scrollTop(minScroll);
        },

        //highlights list item before currently active item
        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();
            }
        },

        //returns index of currently active list item
        getActiveIndex: function() {
            return $.inArray(this.getActive().get(0), this.listItems.filter(".visible").get());
        },

        //scrolls list wrapper up when needed
        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);
            }
        },

        //emptyText stuff
        applyEmptyText: function() {
            if (!this.config.emptyText.length)
                return;

            var self = this;

			this.input.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 stuff
        triggerSelected: function() {
            if (!this.config.triggerSelected)
                return;

            var self = this, found = false;

			this.options.each(function() {
				if ($(this).attr("selected")) {
					self.setComboValue($(this).text(), true, false);
					found = true;
					return false;
				}
			});

			if (!found)
				self.setComboValue(this.options.eq(0).text(), false, false);
        },

        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;
                }
            }        
        },

        //updates dropUp config option
        setDropUp: function(isDropUp) {
            isDropUp ?
                this.listWrapper.addClass("list-wrapper-up") :
                this.listWrapper.removeClass("list-wrapper-up");
        },

        notify: function(evt) {
            if (!$.isFunction(this.config[evt + "Callback"]))
                return;

            this.config[evt + "Callback"].call(this);
        }
    });

    $sc.extend({
        //key codes
        //from jCarousel
        KEY: {
            UP: 38,
            DOWN: 40,
            DEL: 46,
            TAB: 9,
            RETURN: 13,
            ESC: 27,
            COMMA: 188,
            PAGEUP: 33,
            PAGEDOWN: 34,
            BACKSPACE: 8
        },
		setDefaults: function(config){
			defaults = $.extend({}, defaults, config || {});
		}
    });
})(jQuery);


