/*global jQuery */

(function ($) {

    $.widget('ui.searchableMultiSelect', {
        _init: function () {
            this.element.addClass('multiSelectContainer');

            // If no ID is configured use container ID as a base
            if (this.options.elementIdPrepend === 'defaultId') {
                this.options.elementIdPrepend = this.element.attr('id');
            }

            var o = this.options,
                optionList;

            // Find the list of select options and
            // Set as an instance variable accessible in all functions
            if (this.element.children('ol').length > 1) {
                optionList = this.optionList = $('ol#' + o.elementIdPrepend + 'List', this.element);
            } else {
                optionList = this.optionList = $('ol:first', this.element);
            }

            optionList.addClass('multiSelect');

            if (o.allOptionText !== false) {
                optionList.prepend(
                    '<li id="' + o.elementIdPrepend + 'All">' + o.allOptionText + '</li>'
                );
            }

            optionList.children('li').addClass('option');
            if (o.defaultIndex >= 0) {
                this._setDefault();
            }

            // Add the title, if required
            if (o.title !== false) {
                this.element.prepend('<h4>' + o.title + '</h4>');
            }

            // Add hiding controls, supporting markup and binding, if required
            if (o.hideable) {

                this.element.prepend('<a href="#" class="hideFilterLink">hide</a>');
                optionList.wrap('<div class="toHide"></div>');

                $('a.hideFilterLink', this.element).click(function (event) {
                    if ($(this).text() === 'hide') {
                        $(this).text('show');
                        $(this).siblings('div.toHide').hide('normal');
                    } else {
                        $(this).text('hide');
                        $(this).siblings('div.toHide').show('normal');
                    }
                    return false;
                });
            }

            $.each(o.filters, function () {
                // make sure filter list exists
                if ($('ol#' + this.id + 'FilterList').length) {
                    if (this.label !== false) {
                        optionList.before(
                            '<label for="' + this.id + 'Filter">' + this.label + '</label>'
                        );
                    }
                    
                    optionList.before(
                        '<select id="' + this.id + 'Filter" style="display: block"></select>'
                    );

                    var selectFilter = $('select#' + this.id + 'Filter');
                    selectFilter.append(
                        '<option value="0">All</option>'
                    );
                    $('ol#' + this.id + 'FilterList li').each(function () {
                        selectFilter.append(
                            '<option value="' + $(this).attr('id').match(/\d+/)[0] + '">' + $(this).text() + '</option>'
                        );
                    });

                    selectFilter.change(function (event) {
                        $(event.target).parents('div.multiSelectContainer').searchableMultiSelect('filterChange', event);
                    });

                    if (this.hideSelectOnDefault) {
                        optionList.css('display', 'none');
                    }

                    $('ol#' + this.id + 'FilterList li').remove();
                    $('ol#' + this.id + 'FilterList').remove();
                }
            });

            // Add searching functionality, if required
            if (o.searchable) {
                if (o.searchLabel !== false) {
                    optionList.before(
                        '<label for="' + o.elementIdPrepend + 'Search">' + o.searchLabel + '</label>'
                    );
                }

                optionList.before(
                    '<input id="' + o.elementIdPrepend + 'Search" />'
                );

                $('input#' + o.elementIdPrepend + 'Search').keyup(function (event) {
                    $(this).parents('div.multiSelectContainer').searchableMultiSelect('searchOptions', event.target.value);
                });
            }

            if (o.activeOptionsLimit > 0) {
                optionList.after(
                    '<p id="' + o.elementIdPrepend + 'Error" class="errorMessage"></p>'
                );
            }

            // Bind click event
            optionList.click(function (event) {
                // Only do something if an <li> is clicked
                if (event.target.nodeName === 'LI') {
                    $(event.target).parents('div.multiSelectContainer').searchableMultiSelect('optionClick', event);
                }
            });
            
            if (o.allOptionText !== false) {
                this._checkAllOption();
            }

            if (o.showOnCompletion) {
                this.element.css('display', 'block');
            }
        },

        // Returns only the first selected option ID
        getSelectedValue: function () {
            var result = this.getSelectedValues();

            if ($.isArray(result)) {
                return result[0];
            } else {
                return result;
            }
        },

        // returns array of all selected option IDs
        getSelectedValues: function () {
            // If nothing is set, either set a default or return an empty array
            if (this.options.allOptionText !== false &&
                $('li.option.active', this.optionList).length === 0) {
                if (this.options.defaultIndex >= 0) {
                    this._setDefault();
                } else {
                    return [];
                }
            }
            
            if (this.options.multiSelect) {
                // Make prepend available in jQuery loops
                var prepend = this.options.elementIdPrepend,
                    idArray = [],
                    filtersSet = 0;
                // If 'All' option exists and it is selected
                if (this.options.allOptionText !== false &&
                    $('li#' + this.options.elementIdPrepend + 'All.active').length === 1) {
                    // Check if any filters are set
                    $.each(this.options.filters, function () {
                        filtersSet += this.value;
                    });

                    if (filtersSet > 0) {
                        $('li:not(.filtered)', this.optionList).each(function () {
                            idArray.push(this.id.replace(prepend, ""));
                        });
                    } else {
                        return [];
                    }
                } else {
                    $('li.active', this.optionList).each(function () {
                        idArray.push(this.id.replace(prepend, ""));
                    });
                }
                return idArray;
            } else {
                // If not a multiselect return just the first selected option
                return $('li.option.active:first', this.optionList).attr('id').replace(this.options.elementIdPrepend, "");
            }
        },

        optionClick: function (event) {
            // If not multiselect, remove active from all options before setting this one
            if (!this.options.multiSelect) {
                $('li.option', this.optionList).removeClass('active');
            }

            if (this.options.allOptionText !== false &&
                $(event.target).attr('id') === this.options.elementIdPrepend + 'All') {
                // All option is enabled and has been clicked - do pretty much nothing.
                // highlighting this option is done by _checkAllOption at the end of this function
                $('li.option', this.optionList).removeClass('active');
            } else {
                if (this.options.activeOptionsLimit > 0 &&
                   !$(event.target).hasClass("active") &&
                   ($('li.option.active', this.optionList).length === this.options.activeOptionsLimit)) {
                    // Option limit has been reached and current click is to ACTIVATE an option
                    $('p#' + this.options.elementIdPrepend + 'Error').text('selection limit is ' + this.options.activeOptionsLimit);
                    return;
                } else {
                    $('p#' + this.options.elementIdPrepend + 'Error').text('');
                    $(event.target).toggleClass("active");

                    // if required, check that at least one option is set
                    if (this.options.defaultIndex >= 0) {
                        this._setDefault();
                    }

                    if (this.options.multiSelect && this.options.fillInRange) {
                        this._fillInRange();
                    }
                }
            }
            if (this.options.allOptionText !== false) {
                this._checkAllOption();
            }

            var selectFilters = [];

            $.each(this.options.filters, function () {
                selectFilters[this.id] = this.value;
            });

            return this.options.optionClickFunction(this.getSelectedValues(), selectFilters);
        },

        // Option searching provided as an external function
        searchOptions: function (searchString) {
            this.options.searchString = searchString;
            this._applyFilters();
        },

        filterChange: function (event) {
            // Get the filter type and value
            this.options.filters[$(event.target).attr('id').replace('Filter', '')].value = $(event.target).attr('value');
            // Hide multi select if this filter is set to 'all'
            if (this.options.filters[$(event.target).attr('id').replace('Filter', '')].hideSelectOnDefault &&
                $(event.target).attr('value') === "0") {
                this.optionList.css('display', 'none');
                // Deactiveate all options so avoid current selection being hidden from the user
                $('li.option', this.optionList).removeClass('active');
            } else {
                this.optionList.css('display', 'block');
            }

            this._applyFilters();

            var selectFilters = [];

            $.each(this.options.filters, function () {
                selectFilters[this.id] = this.value;
            });

            return this.options.optionClickFunction(this.getSelectedValues(), selectFilters);
        },

        // Private function to apply filters on the options
        _applyFilters: function () {
            // Create class string from set filters
            var classString = '',
                searchExpression;
            $.each(this.options.filters, function () {
                if (this.value > 0) {
                    classString += '.' + this.id + '' + this.value;
                }
            });

            // Hide all options
            $('li', this.optionList).addClass('filtered');
            // De-active all currently active items
            $('li.active', this.optionList).addClass('exActive').removeClass('active');

            if (this.options.searchString === '') {
                $('li' + classString, this.optionList).removeClass('filtered');
                $('li' + classString + '.exActive', this.optionList).addClass('active');
            } else {
                // Regex of search string, matches from start of string, case insensitive
                searchExpression = new RegExp('^' + this.options.searchString, 'i');
                // Loop over options
                $('li' + classString, this.optionList).each(function () {
                    if (searchExpression.test($.trim($(this).text()))) {
                        // If option matches, show it
                        $(this).removeClass('filtered');
                        // All previously active options that match keep as active
                        if ($(this).hasClass('exActive')) {
                            $(this).addClass('active');
                        }
                    }

                });
            }
            if (this.options.allOptionText !== false) {
                this._checkAllOption();
            }
            // Clear exActive flags
            this.optionList.children('li').removeClass('exActive');
        },

        _checkAllOption: function () {
            // Show 'all' option if nothing is typed
            if (this.options.searchString === '') {
                $('li#' + this.options.elementIdPrepend + 'All', this.optionList).removeClass('filtered');
            } else {
                // Remove 'all' option if something has been typed
                $('li#' + this.options.elementIdPrepend + 'All', this.optionList).addClass('filtered');
            }

            // activate the 'all' option if required
            if ($('li.active:not(.filtered)', this.optionList).length === 0) {
                $('li#' + this.options.elementIdPrepend + 'All', this.optionList).addClass('active');
            } else {
                $('li#' + this.options.elementIdPrepend + 'All', this.optionList).removeClass('active');
            }
        },

        // Private function to fill in options between first and last selections
        _fillInRange: function () {
            var firstActiveIndex = -1,
                lastActiveIndex = -1;
            // Find the indexes of the first and last active optins
            $('li', this.optionList).each(function (i) {
                if ($(this).hasClass("active")) {
                    if (firstActiveIndex === -1) {
                        firstActiveIndex = i;
                    }
                    lastActiveIndex = i;
                }
            });
            // If something is selected then fill in all the options in between
            if (firstActiveIndex > -1 && lastActiveIndex > -1) {
                $('li', this.optionList).slice(firstActiveIndex, lastActiveIndex).addClass("active");
            }
        },

        _setDefault: function () {
            // If default is required and nothing is currently active, set first option as active
            if ($('li.option.active', this.optionList).length === 0) {
                $('li.option:first', this.optionList).addClass('active');
            }
        }
    });


    $.extend($.ui.searchableMultiSelect, {
        version: "0.9",
        defaults: {
            title: 'Multiselect',
            optionClickFunction: function () {},
            elementIdPrepend: 'defaultId',
            searchLabel: 'Search: ',
            searchable: true,
            searchString: '',
            hideable: true,
            fillInRange: false,
            multiSelect: true,
            defaultIndex: -1,
            showOnCompletion: true,
            activeOptionsLimit: 0,
            filters: [],
            allOptionText: false
        },
        getter: 'getSelectedValue getSelectedValues'
    });


}(jQuery));
