/**
 * Converts SELECT into autocompletion field
 * Can handle singe or multiple SELECT
 *
 * Author: Rostislav Bryzgunov
 * E-mail: kottenator@gmail.com
 *
 * @options
 *     invalidClass - CSS class that will be added to the input field if it's invalid. Default is 'invalid-field-value'
 *     mode - 'slug': send 'field_slug=aaa',
              'value': send 'field=1',
              'both': send 'field=1&field_slug=aaa' - where aaa <-> 1 (same item)
              'add': send 'field=1&field=2&field_slug=zzz' - where zzz is new (not existent) item
 *     strict - should items be only from list or could be unknown? Default - false
 *     initial - initial value of the field. If null or undefined - SELECT > OPTION[selected] will be used
 *
 * @requires jquery.ui.core ($.widget)
 */

$.widget("ui.autoselect", {
    name: '',
    input: null,
    source: null,
    selected: null,
    textToValue: null,

    options: {
        invalidClass: 'ui-invalid-field-value',
        mode: 'add',
        strict: false,
        filterMethod: 'contains', // or may be 'startsWith'
        initial: null
    },

    _create: function() {
        if (!this.element[0] || this.element[0].tagName.toLowerCase() != 'select' || this.inited)
            return;

        this.name = this.element.attr("name");
        this.element.hide()[0].disabled = true;
        var input_name = this.name + '_fake';
        this.input = $('<input type="text" name="' + input_name + '" />').insertAfter(this.element);
        $(this.input[0].form).submit($.proxy(this, '_updateValues'));

        this._initValues();
        this._updateValues();

        if (this.element[0].multiple)
            this._initMultipleChoice();
        else
            this._initSingleChoice();
    },

    _initValues: function() {
        var source = this.source = [],
            selected = [],
            textToValue = this.textToValue = {};

        $(this.element[0].options).each(function() {
            var text = this.text,
                value = this.value;
            source.push(text);
            if (this.selected)
                selected.push(text);
            textToValue[text] = value;
        });

        var initial = this.options.initial;
        if (typeof initial != "undefined" && initial != null)
            this.input.val(initial);
        else
            this.input.val(selected.join(', '));
    },

    _updateValues: function() {
        var $this = this,
            mode = this.options.mode,
            input = this.input,
            selected = this.utils.split(input.val()); // already cleaned from ''

        input.parent().find('input[type="hidden"][name="' + this.name + '"]').remove();
        input.parent().find('input[type="hidden"][name="' + this.name + '_slug"]').remove();

        var newPresented = false;
        $(selected).each(function() {
            var text = this,
                value = $this.textToValue[text];

            if (typeof value != "undefined" && value != null) {
                if (mode == 'value' || mode == 'both' || mode == 'add')
                    $('<input type="hidden" name="' + $this.name + '" value="' + value + '" />').insertAfter(input);
            } else {
                if (mode == 'add')
                    $('<input type="hidden" name="' + $this.name + '_slug" value="' + text + '" />').insertAfter(input);
                newPresented = true;
            }

            if (mode == 'slug' || mode == 'both')
                $('<input type="hidden" name="' + $this.name + '_slug" value="' + text + '" />').insertAfter(input);
        });

        if (newPresented && this.options.strict)
            input.addClass(this.options.invalidClass);
        else
            input.removeClass(this.options.invalidClass);
    },

    _initSingleChoice: function() {
        var $this = this;
        this.input.autocomplete({
            minLength: 0,
            source: function(request, response) {
                response($this.utils[$this.options.filterMethod]($this.source, request.term));
            },
            select: function(e, ui) {
                $this._updateValues();
            }
        })
        .bind('keydown', function(e) {
            if (e.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
                e.preventDefault();
            }
        })
        .change($.proxy(this, '_updateValues'));
    },

    _initMultipleChoice: function() {
        var $this = this;
        this.input.autocomplete({
            minLength: 0,
            source: function(request, response) {
                response($this.utils[$this.options.filterMethod]($this.source, $this.utils.extractLast(request.term)));
            },
            focus: function() {
                return false;
            },
            select: function(e, ui) {
                var terms = $this.utils.split(this.value);
                terms.pop();
                terms.push(ui.item.value);
                terms.push("");
                this.value = terms.join(", ");
                return false;
            }
        })
        .bind('keydown', function(e) {
            if (e.keyCode === $.ui.keyCode.TAB && $(this).data("autocomplete").menu.active) {
                e.preventDefault();
            }
        })
        .change($.proxy(this, '_updateValues'));
    },

    utils: {
        trim: function(str) {
            return str.replace(/(^\s+|\s+$)/g, '');
        },

        cleanEmpty: function(arr) {
            var newArray = [];
            for (var i = 0, l = arr.length; i < l; i++) {
                var val = this.trim(arr[i]);
                if (val)
                    newArray.push(val);
            }
            return newArray;
        },

        split: function(str) {
            return this.cleanEmpty(str.split(/,\s*/));
        },

        extractLast: function(term) {
            return this.split(term).pop() || ""; // .split(...) may return [] due to using .cleanEmpty(...)
        },

        contains: $.ui.autocomplete.filter,
        startsWith: function(array, term) {
            var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(term), 'i');
            return $.grep(array, function(value) {
                return matcher.test(value.label || value.value || value);
            });
        }
    }
});
