﻿(function($) {
    $.fn.TagSuggest = function(options) {
        // Set the options.
        var opts = $.extend({}, $.fn.TagSuggest.defaults, options);

        // Go through the matched elements and return the jQuery object.
        return this.each(function() {
            // Extend our options object with metadata from the DOM
            var o = $.metadata ? $.extend({}, opts, $(this).metadata()) : opts;

            var tagInput = $(this); //Input element
            var tagInputElm = this;
            var tagBtns = $('.' + o.tagButtonClass); //Tag Input Buttons
            var tagSuggest = $('<ul/>').addClass(o.suggestClass); // Popup Suggestion element            

            // Bind the click event on our tag buttons
            tagBtns.click(function(ev) {
                var isActive = $(this).hasClass('active');
                var selectedTag = $(this).val();

                // Split and check for any tags that need to be removed
                var tags = [];
                $.each(tagInput.val().split(o.separator), function(i, value) {
                    var trimTag = $.trim(value);
                    if (trimTag.length > 0 && !(isActive && trimTag.toLowerCase() == selectedTag.toLowerCase()))
                        tags[tags.length] = trimTag;
                });

                // If user selected a new tag add it to our tags
                if (!isActive)
                    tags[tags.length] = selectedTag;

                // Rejoin our tags
                var tagString = tags.join(o.separator + ' ');
                if (tagString.length > 0) tagString += o.separator + ' ';

                tagInput.val(tagString).focus();
                o.onChange.call(tagInputElm);

                hideSuggestions();
                showActiveBtns();
                ev.preventDefault();
            });

            // Bind the keydown(or if opera keypress) & keyup event on our tag Input
            tagInput.bind(($.browser.opera ? 'keypress' : 'keydown'), function(ev) {
                switch (ev.keyCode) {
                    case Key.Tab:
                    case Key.Return:
                        {
                            if (visible(tagSuggest) && tagSuggest.find('.active').length > 0) {
                                selectTag(tagSuggest.find('.active').data('tag_data'));
                                showActiveBtns();
                            }

                            ev.preventDefault();
                            o.onChange.call(tagInputElm);
                            break;
                        }
                    case Key.Up:
                        {
                            var currSuggestion = tagSuggest.find('.active');
                            var prevSuggestion = currSuggestion.prev();

                            if (prevSuggestion.length > 0) {
                                currSuggestion.removeClass('active')
                                prevSuggestion.addClass('active');
                            }

                            ev.preventDefault();
                            break;
                        }
                    case Key.Down:
                        {
                            var currSuggestion = tagSuggest.find('.active');
                            var nextSuggestion = currSuggestion.next()

                            if (nextSuggestion.length > 0) {
                                currSuggestion.removeClass('active')
                                nextSuggestion.addClass('active');
                            }

                            ev.preventDefault();
                            break;
                        }
                    case Key.Esc:
                        {
                            hideSuggestions();
                            ev.preventDefault();
                            break;
                        }
                }
            }).keyup(function(ev) {
                switch (ev.keyCode) {
                    case Key.Up:
                    case Key.Down:
                    case Key.Esc:
                    case Key.Tab:
                    case Key.Return:
                        {
                            break;
                        }
                    default:
                        {
                            //Todo: functions both iterate over tagBtns, suggest refactoring at a 
                            //later stage to improve performance. Left in this state to 
                            //improve readablity of code
                            showActiveBtns();
                            var tagMatches = getSuggestions();

                            if (tagMatches.length > 0)
                                showSuggestions(tagMatches);
                            else
                                hideSuggestions();

                            o.onChange.call(tagInputElm);

                            break;
                        }
                }
            });

            // Bind the click & mouseover event on our tag Suggestion box
            tagSuggest.click(function(ev) {
                selectTag($(ev.target).data('tag_data'));
                o.onChange.call(tagInputElm);
            }).mouseover(function(ev) {
                if (target(ev).nodeName && target(ev).nodeName.toUpperCase() == 'LI') {
                    $('li', tagSuggest).removeClass('active')
                    $(target(ev)).addClass('active');
                }
            });

            // Adds selected tag to the tag input
            function selectTag(tag) {
                var tags = splitTags(tagInput.val());

                tags[tags.length - 1] = tag;

                var tagString = tags.join(o.separator + ' ');
                tagInput.val(tagString + o.separator + ' ').focus();

                hideSuggestions();
            }

            // Hides suggestion box
            function hideSuggestions() {
                $(tagSuggest).css('display', 'none');
                $(tagSuggest).empty();
            }

            // Takes an array of tag matches, builds the suggestion box then shows it
            function showSuggestions(tagMatches) {
                tagSuggest.empty();
                $.each(tagMatches, function(i, value) {
                    var span = $('<span/>').html(value.title + ' items');
                    var li = $('<li/>').html(value.value).append(span).appendTo(tagSuggest);
                    li.data('tag_data', value.value);
                });

                var offset = tagInput.offset();

                tagSuggest.css({
                    top: offset.top + tagInput.outerHeight(),
                    left: offset.left + (($.browser.mozilla && tagInput[0].tagName == 'INPUT') ? 1 : 0) //hack because for some reason mozilla is out by 1px with inputs
                }).show();
                tagSuggest.appendTo(document.body);

                // Highlight first option
                var tags = tagSuggest.find('li');
                tags.slice(0, 1).addClass('active');
            }

            // Iterates over all the tag buttons and checks to see whether any are in the tag input
            // if they are then they are set as active if not they are set as inactive
            function showActiveBtns() {
                var tagInputArray = splitTags(tagInput.val());

                tagBtns.each(function() {
                    var tagBtn = $(this);

                    if ($.inArray(tagBtn.val(), tagInputArray) >= 0)
                        tagBtn.addClass('active');
                    else
                        tagBtn.removeClass('active');
                });
            }

            // Grabs the users input so far and trys to find matches with our tag buttons
            function getSuggestions() {
                var tagInputArray = splitTags($(tagInput).val());

                // grab the last word entered by the user
                var endTag = tagInputArray[tagInputArray.length - 1];
                var tagMatches = []

                tagBtns.each(function() {
                    var tagBtn = $(this);
                    // grab any matches with the users current input                                       
                    if (tagBtn.val().indexOf(endTag) == 0 && endTag.length > 0 && this.title && (tagBtn.val() != endTag))
                        tagMatches[tagMatches.length] = this;
                });

                return tagMatches;
            }

            function splitTags(tagString) {
                var tagArray = [];

                $.each(tagString.toLowerCase().split(o.separator), function(i, value) {
                    var trimTag = $.trim(value);
                    if (trimTag.length > 0)
                        tagArray[tagArray.length] = trimTag;
                });

                return tagArray;
            }
        });
    };

    // Global Varialbles
    var Key = {
        Left: 37,
        Up: 38,
        Right: 39,
        Down: 40,
        Del: 46,
        Tab: 9,
        Return: 13,
        Esc: 27,
        Comma: 188,
        PageUp: 33,
        PageDown: 34,
        BackSpace: 8
    };

    // Private Functions
    function visible(element) {
        return element && element.is(":visible");
    };

    function target(ev) {
        var element = ev.target;
        while (element && element.tagName != "LI")
            element = element.parentNode;
        // more fun with IE, sometimes event.target is empty, just ignore it then
        if (!element)
            return [];
        return element;
    }

    // Public defaults.
    $.fn.TagSuggest.defaults = {
        tagButtonClass: 'tag-button',
        suggestClass: 'tag-suggest',
        separator: ',',
        onChange: function() { }
    };
})(jQuery);
