/**
 * Bootstrap Multiselect v0.9.8 (https://github.com/davidstutz/bootstrap-multiselect)
 *
 * Copyright 2012 - 2014 David Stutz
 *
 * Dual licensed under the BSD-3-Clause and the Apache License, Version 2.0.
 */
! function($) {

   "use strict"; // jshint ;_;

   if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
      ko.bindingHandlers.multiselect = {

         init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

            var listOfSelectedItems = allBindingsAccessor().selectedOptions;
            var config = ko.utils.unwrapObservable(valueAccessor());

            $(element).multiselect(config);

            if (isObservableArray(listOfSelectedItems)) {

               // Set the initial selection state on the multiselect list.
               $(element).multiselect('select', ko.utils.unwrapObservable(listOfSelectedItems));

               // Subscribe to the selectedOptions: ko.observableArray
               listOfSelectedItems.subscribe(function(changes) {
                  var addedArray = [],
                     deletedArray = [];
                  forEach(changes, function(change) {
                     switch (change.status) {
                        case 'added':
                           addedArray.push(change.value);
                           break;
                        case 'deleted':
                           deletedArray.push(change.value);
                           break;
                     }
                  });

                  if (addedArray.length > 0) {
                     $(element).multiselect('select', addedArray);
                  }

                  if (deletedArray.length > 0) {
                     $(element).multiselect('deselect', deletedArray);
                  }
               }, null, "arrayChange");
            }
         },

         update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

            var listOfItems = allBindingsAccessor().options,
               ms = $(element).data('multiselect'),
               config = ko.utils.unwrapObservable(valueAccessor());

            if (isObservableArray(listOfItems)) {
               // Subscribe to the options: ko.observableArray incase it changes later
               listOfItems.subscribe(function(theArray) {
                  $(element).multiselect('rebuild');
               });
            }

            if (!ms) {
               $(element).multiselect(config);
            } else {
               ms.updateOriginalOptions();
            }
         }
      };
   }

   function isObservableArray(obj) {
      return ko.isObservable(obj) && !(obj.destroyAll === undefined);
   }

   function forEach(array, callback) {
      for (var index = 0; index < array.length; ++index) {
         callback(array[index]);
      }
   }

   /**
    * Constructor to create a new multiselect using the given select.
    *
    * @param {jQuery} select
    * @param {Object} options
    * @returns {Multiselect}
    */
   function Multiselect(select, options) {

      this.$select = $(select);
      this.options = this.mergeOptions($.extend({}, options, this.$select.data()));

      // Initialization.
      // We have to clone to create a new reference.
      this.originalOptions = this.$select.clone()[0].options;
      this.query = '';
      this.searchTimeout = null;

      this.options.multiple = this.$select.attr('multiple') === "multiple";
      this.options.onChange = $.proxy(this.options.onChange, this);
      this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
      this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
      this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
      this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);

      // Build select all if enabled.
      this.buildContainer();
      this.buildButton();
      this.buildDropdown();
      this.buildSelectAll();
      this.buildDropdownOptions();
      this.buildFilter();

      this.updateButtonText();
      this.updateSelectAll();

      if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
         this.disable();
      }

      this.$select.hide().after(this.$container);
   };

   Multiselect.prototype = {

      defaults: {
         /**
          * Default text function will either print 'None selected' in case no
          * option is selected or a list of the selected options up to a length
          * of 3 selected options.
          *
          * @param {jQuery} options
          * @param {jQuery} select
          * @returns {String}
          */
         buttonText: function(options, select) {
            if (options.length === 0) {
               return this.nonSelectedText + ' <b class="caret"></b>';
            } else if (options.length == $('option', $(select)).length) {
               return this.allSelectedText + ' <b class="caret"></b>';
            } else if (options.length > this.numberDisplayed) {
               return options.length + ' ' + this.nSelectedText + ' <b class="caret"></b>';
            } else {
               var selected = '';
               options.each(function() {
                  var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).html();

                  selected += label + ', ';
               });

               return selected.substr(0, selected.length - 2) + ' <b class="caret"></b>';
            }
         },
         /**
          * Updates the title of the button similar to the buttonText function.
          *
          * @param {jQuery} options
          * @param {jQuery} select
          * @returns {@exp;selected@call;substr}
          */
         buttonTitle: function(options, select) {
            if (options.length === 0) {
               return this.nonSelectedText;
            } else {
               var selected = '';
               options.each(function() {
                  selected += $(this).text() + ', ';
               });
               return selected.substr(0, selected.length - 2);
            }
         },
         /**
          * Create a label.
          *
          * @param {jQuery} element
          * @returns {String}
          */
         label: function(element) {
            return $(element).attr('label') || $(element).html();
         },
         /**
          * Triggered on change of the multiselect.
          *
          * Not triggered when selecting/deselecting options manually.
          *
          * @param {jQuery} option
          * @param {Boolean} checked
          */
         onChange: function(option, checked) {

         },
         /**
          * Triggered when the dropdown is shown.
          *
          * @param {jQuery} event
          */
         onDropdownShow: function(event) {

         },
         /**
          * Triggered when the dropdown is hidden.
          *
          * @param {jQuery} event
          */
         onDropdownHide: function(event) {

         },
         /**
          * Triggered after the dropdown is shown.
          *
          * @param {jQuery} event
          */
         onDropdownShown: function(event) {

         },
         /**
          * Triggered after the dropdown is hidden.
          *
          * @param {jQuery} event
          */
         onDropdownHidden: function(event) {

         },
         buttonClass: 'btn btn-default',
         buttonWidth: 'auto',
         buttonContainer: '<div class="btn-group" />',
         dropRight: false,
         selectedClass: 'active',
         // Maximum height of the dropdown menu.
         // If maximum height is exceeded a scrollbar will be displayed.
         maxHeight: false,
         checkboxName: false,
         includeSelectAllOption: false,
         includeSelectAllIfMoreThan: 0,
         selectAllText: ' Select all',
         selectAllValue: 'multiselect-all',
         selectAllName: false,
         enableFiltering: false,
         enableCaseInsensitiveFiltering: false,
         enableClickableOptGroups: false,
         filterPlaceholder: 'Search',
         // possible options: 'text', 'value', 'both'
         filterBehavior: 'text',
         includeFilterClearBtn: true,
         preventInputChangeEvent: false,
         nonSelectedText: 'None selected',
         nSelectedText: 'selected',
         allSelectedText: 'All selected',
         numberDisplayed: 3,
         disableIfEmpty: false,
         templates: {
            button: '<button type="button" class="multiselect dropdown-toggle" data-toggle="dropdown"></button>',
            ul: '<ul class="multiselect-container dropdown-menu"></ul>',
            filter: '<li class="multiselect-item filter"><div class="input-group"><span class="input-group-addon"><i class="glyphicon glyphicon-search"></i></span><input class="form-control multiselect-search" type="text"></div></li>',
            filterClearBtn: '<span class="input-group-btn"><button class="btn btn-default multiselect-clear-filter" type="button"><i class="glyphicon glyphicon-remove"></i></button></span>',
            li: '<li><a href="javascript:void(0);"><label></label></a></li>',
            divider: '<li class="multiselect-item divider"></li>',
            liGroup: '<li class="multiselect-item multiselect-group"><label></label></li>'
         }
      },

      constructor: Multiselect,

      /**
       * Builds the container of the multiselect.
       */
      buildContainer: function() {
         this.$container = $(this.options.buttonContainer);
         this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
         this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
         this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
         this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
      },

      /**
       * Builds the button of the multiselect.
       */
      buildButton: function() {
         this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);

         // Adopt active state.
         if (this.$select.prop('disabled')) {
            this.disable();
         } else {
            this.enable();
         }

         // Manually add button width if set.
         if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
            this.$button.css({
               'width': this.options.buttonWidth
            });
            this.$container.css({
               'width': this.options.buttonWidth
            });
         }

         // Keep the tab index from the select.
         var tabindex = this.$select.attr('tabindex');
         if (tabindex) {
            this.$button.attr('tabindex', tabindex);
         }

         this.$container.prepend(this.$button);
      },

      /**
       * Builds the ul representing the dropdown menu.
       */
      buildDropdown: function() {

         // Build ul.
         this.$ul = $(this.options.templates.ul);

         if (this.options.dropRight) {
            this.$ul.addClass('pull-right');
         }

         // Set max height of dropdown menu to activate auto scrollbar.
         if (this.options.maxHeight) {
            // TODO: Add a class for this option to move the css declarations.
            this.$ul.css({
               'max-height': this.options.maxHeight + 'px',
               'overflow-y': 'auto',
               'overflow-x': 'hidden'
            });
         }

         this.$container.append(this.$ul);
      },

      /**
       * Build the dropdown options and binds all nessecary events.
       *
       * Uses createDivider and createOptionValue to create the necessary options.
       */
      buildDropdownOptions: function() {

         this.$select.children().each($.proxy(function(index, element) {

            var $element = $(element);
            // Support optgroups and options without a group simultaneously.
            var tag = $element.prop('tagName')
               .toLowerCase();

            if ($element.prop('value') === this.options.selectAllValue) {
               return;
            }

            if (tag === 'optgroup') {
               this.createOptgroup(element);
            } else if (tag === 'option') {

               if ($element.data('role') === 'divider') {
                  this.createDivider();
               } else {
                  this.createOptionValue(element);
               }

            }

            // Other illegal tags will be ignored.
         }, this));

         // Bind the change event on the dropdown elements.
         $('li input', this.$ul).on('change', $.proxy(function(event) {
            var $target = $(event.target);

            var checked = $target.prop('checked') || false;
            var isSelectAllOption = $target.val() === this.options.selectAllValue;

            // Apply or unapply the configured selected class.
            if (this.options.selectedClass) {
               if (checked) {
                  $target.closest('li')
                     .addClass(this.options.selectedClass);
               } else {
                  $target.closest('li')
                     .removeClass(this.options.selectedClass);
               }
            }

            // Get the corresponding option.
            var value = $target.val();
            var $option = this.getOptionByValue(value);

            var $optionsNotThis = $('option', this.$select).not($option);
            var $checkboxesNotThis = $('input', this.$container).not($target);

            if (isSelectAllOption) {
               if (checked) {
                  this.selectAll();
               } else {
                  this.deselectAll();
               }
            }

            if (!isSelectAllOption) {
               if (checked) {
                  $option.prop('selected', true);

                  if (this.options.multiple) {
                     // Simply select additional option.
                     $option.prop('selected', true);
                  } else {
                     // Unselect all other options and corresponding checkboxes.
                     if (this.options.selectedClass) {
                        $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass);
                     }

                     $($checkboxesNotThis).prop('checked', false);
                     $optionsNotThis.prop('selected', false);

                     // It's a single selection, so close.
                     this.$button.click();
                  }

                  if (this.options.selectedClass === "active") {
                     $optionsNotThis.closest("a").css("outline", "");
                  }
               } else {
                  // Unselect option.
                  $option.prop('selected', false);
               }
            }

            this.$select.change();

            this.updateButtonText();
            this.updateSelectAll();

            this.options.onChange($option, checked);

            if (this.options.preventInputChangeEvent) {
               return false;
            }
         }, this));

         $('li a', this.$ul).on('touchstart click', function(event) {
            event.stopPropagation();

            var $target = $(event.target);

            if (document.getSelection().type === 'Range') {
               var $input = $(this).find("input:first");

               $input.prop("checked", !$input.prop("checked"))
                  .trigger("change");
            }

            if (event.shiftKey) {
               var checked = $target.prop('checked') || false;

               if (checked) {
                  var prev = $target.closest('li')
                     .siblings('li[class="active"]:first');

                  var currentIdx = $target.closest('li')
                     .index();
                  var prevIdx = prev.index();

                  if (currentIdx > prevIdx) {
                     $target.closest("li").prevUntil(prev).each(
                        function() {
                           $(this).find("input:first").prop("checked", true)
                              .trigger("change");
                        }
                     );
                  } else {
                     $target.closest("li").nextUntil(prev).each(
                        function() {
                           $(this).find("input:first").prop("checked", true)
                              .trigger("change");
                        }
                     );
                  }
               }
            }

            $target.blur();
         });

         // Keyboard support.
         this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
            if ($('input[type="text"]', this.$container).is(':focus')) {
               return;
            }

            if (event.keyCode === 9 && this.$container.hasClass('open')) {
               this.$button.click();
            } else {
               var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");

               if (!$items.length) {
                  return;
               }

               var index = $items.index($items.filter(':focus'));

               // Navigation up.
               if (event.keyCode === 38 && index > 0) {
                  index--;
               }
               // Navigate down.
               else if (event.keyCode === 40 && index < $items.length - 1) {
                  index++;
               } else if (!~index) {
                  index = 0;
               }

               var $current = $items.eq(index);
               $current.focus();

               if (event.keyCode === 32 || event.keyCode === 13) {
                  var $checkbox = $current.find('input');

                  $checkbox.prop("checked", !$checkbox.prop("checked"));
                  $checkbox.change();
               }

               event.stopPropagation();
               event.preventDefault();
            }
         }, this));

         if (this.options.enableClickableOptGroups && this.options.multiple) {
            $('li.multiselect-group', this.$ul).on('click', $.proxy(function(event) {
               event.stopPropagation();

               var group = $(event.target).parent();

               // Search all option in optgroup
               var $options = group.nextUntil('li.multiselect-group');

               // check or uncheck items
               var allChecked = true;
               var optionInputs = $options.find('input');
               optionInputs.each(function() {
                  allChecked = allChecked && $(this).prop('checked');
               });

               optionInputs.prop('checked', !allChecked).trigger('change');
            }, this));
         }
      },

      /**
       * Create an option using the given select option.
       *
       * @param {jQuery} element
       */
      createOptionValue: function(element) {
         var $element = $(element);
         if ($element.is(':selected')) {
            $element.prop('selected', true);
         }

         // Support the label attribute on options.
         var label = this.options.label(element);
         var value = $element.val();
         var inputType = this.options.multiple ? "checkbox" : "radio";

         var $li = $(this.options.templates.li);
         var $label = $('label', $li);
         $label.addClass(inputType);

         var $checkbox = $('<input/>').attr('type', inputType);

         if (this.options.checkboxName) {
            $checkbox.attr('name', this.options.checkboxName);
         }
         $label.append($checkbox);

         var selected = $element.prop('selected') || false;
         $checkbox.val(value);

         if (value === this.options.selectAllValue) {
            $li.addClass("multiselect-item multiselect-all");
            $checkbox.parent().parent()
               .addClass('multiselect-all');
         }

         $label.append(" " + label);
         $label.attr('title', $element.attr('title'));

         this.$ul.append($li);

         if ($element.is(':disabled')) {
            $checkbox.attr('disabled', 'disabled')
               .prop('disabled', true)
               .closest('a')
               .attr("tabindex", "-1")
               .closest('li')
               .addClass('disabled');
         }

         $checkbox.prop('checked', selected);

         if (selected && this.options.selectedClass) {
            $checkbox.closest('li')
               .addClass(this.options.selectedClass);
         }
      },

      /**
       * Creates a divider using the given select option.
       *
       * @param {jQuery} element
       */
      createDivider: function(element) {
         var $divider = $(this.options.templates.divider);
         this.$ul.append($divider);
      },

      /**
       * Creates an optgroup.
       *
       * @param {jQuery} group
       */
      createOptgroup: function(group) {
         var groupName = $(group).prop('label');

         // Add a header for the group.
         var $li = $(this.options.templates.liGroup);
         $('label', $li).text(groupName);

         if (this.options.enableClickableOptGroups) {
            $li.addClass('multiselect-group-clickable');
         }

         this.$ul.append($li);

         if ($(group).is(':disabled')) {
            $li.addClass('disabled');
         }

         // Add the options of the group.
         $('option', group).each($.proxy(function(index, element) {
            this.createOptionValue(element);
         }, this));
      },

      /**
       * Build the selct all.
       *
       * Checks if a select all has already been created.
       */
      buildSelectAll: function() {
         if (typeof this.options.selectAllValue === 'number') {
            this.options.selectAllValue = this.options.selectAllValue.toString();
         }

         var alreadyHasSelectAll = this.hasSelectAll();

         if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {

            // Check whether to add a divider after the select all.
            if (this.options.includeSelectAllDivider) {
               this.$ul.prepend($(this.options.templates.divider));
            }

            var $li = $(this.options.templates.li);
            $('label', $li).addClass("checkbox");

            if (this.options.selectAllName) {
               $('label', $li).append('<input type="checkbox" name="' + this.options.selectAllName + '" />');
            } else {
               $('label', $li).append('<input type="checkbox" />');
            }

            var $checkbox = $('input', $li);
            $checkbox.val(this.options.selectAllValue);

            $li.addClass("multiselect-item multiselect-all");
            $checkbox.parent().parent()
               .addClass('multiselect-all');

            $('label', $li).append(" " + this.options.selectAllText);

            this.$ul.prepend($li);

            $checkbox.prop('checked', false);
         }
      },

      /**
       * Builds the filter.
       */
      buildFilter: function() {

         // Build filter if filtering OR case insensitive filtering is enabled and the number of options exceeds (or equals) enableFilterLength.
         if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
            var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);

            if (this.$select.find('option').length >= enableFilterLength) {

               this.$filter = $(this.options.templates.filter);
               $('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);

               // Adds optional filter clear button
               if (this.options.includeFilterClearBtn) {
                  var clearBtn = $(this.options.templates.filterClearBtn);
                  clearBtn.on('click', $.proxy(function(event) {
                     clearTimeout(this.searchTimeout);
                     this.$filter.find('.multiselect-search').val('');
                     $('li', this.$ul).show().removeClass("filter-hidden");
                     this.updateSelectAll();
                  }, this));
                  this.$filter.find('.input-group').append(clearBtn);
               }

               this.$ul.prepend(this.$filter);

               this.$filter.val(this.query).on('click', function(event) {
                  event.stopPropagation();
               }).on('input keydown', $.proxy(function(event) {
                  // Cancel enter key default behaviour
                  if (event.which === 13) {
                     event.preventDefault();
                  }

                  // This is useful to catch "keydown" events after the browser has updated the control.
                  clearTimeout(this.searchTimeout);

                  this.searchTimeout = this.asyncFunction($.proxy(function() {

                     if (this.query !== event.target.value) {
                        this.query = event.target.value;

                        var currentGroup, currentGroupVisible;
                        $.each($('li', this.$ul), $.proxy(function(index, element) {
                           var value = $('input', element).val();
                           var text = $('label', element).text();

                           var filterCandidate = '';
                           if ((this.options.filterBehavior === 'text')) {
                              filterCandidate = text;
                           } else if ((this.options.filterBehavior === 'value')) {
                              filterCandidate = value;
                           } else if (this.options.filterBehavior === 'both') {
                              filterCandidate = text + '\n' + value;
                           }

                           if (value !== this.options.selectAllValue && text) {
                              // By default lets assume that element is not
                              // interesting for this search.
                              var showElement = false;

                              if (this.options.enableCaseInsensitiveFiltering && filterCandidate.toLowerCase().indexOf(this.query.toLowerCase()) > -1) {
                                 showElement = true;
                              } else if (filterCandidate.indexOf(this.query) > -1) {
                                 showElement = true;
                              }

                              // Toggle current element (group or group item) according to showElement boolean.
                              $(element).toggle(showElement).toggleClass('filter-hidden', !showElement);

                              // Differentiate groups and group items.
                              if ($(element).hasClass('multiselect-group')) {
                                 // Remember group status.
                                 currentGroup = element;
                                 currentGroupVisible = showElement;
                              } else {
                                 // Show group name when at least one of its items is visible.
                                 if (showElement) {
                                    $(currentGroup).show().removeClass('filter-hidden');
                                 }

                                 // Show all group items when group name satisfies filter.
                                 if (!showElement && currentGroupVisible) {
                                    $(element).show().removeClass('filter-hidden');
                                 }
                              }
                           }
                        }, this));
                     }

                     this.updateSelectAll();
                  }, this), 300, this);
               }, this));
            }
         }
      },

      /**
       * Unbinds the whole plugin.
       */
      destroy: function() {
         this.$container.remove();
         this.$select.show();
         this.$select.data('multiselect', null);
      },

      /**
       * Refreshs the multiselect based on the selected options of the select.
       */
      refresh: function() {
         $('option', this.$select).each($.proxy(function(index, element) {
            var $input = $('li input', this.$ul).filter(function() {
               return $(this).val() === $(element).val();
            });

            if ($(element).is(':selected')) {
               $input.prop('checked', true);

               if (this.options.selectedClass) {
                  $input.closest('li')
                     .addClass(this.options.selectedClass);
               }
            } else {
               $input.prop('checked', false);

               if (this.options.selectedClass) {
                  $input.closest('li')
                     .removeClass(this.options.selectedClass);
               }
            }

            if ($(element).is(":disabled")) {
               $input.attr('disabled', 'disabled')
                  .prop('disabled', true)
                  .closest('li')
                  .addClass('disabled');
            } else {
               $input.prop('disabled', false)
                  .closest('li')
                  .removeClass('disabled');
            }
         }, this));

         this.updateButtonText();
         this.updateSelectAll();
      },

      /**
       * Select all options of the given values.
       *
       * If triggerOnChange is set to true, the on change event is triggered if
       * and only if one value is passed.
       *
       * @param {Array} selectValues
       * @param {Boolean} triggerOnChange
       */
      select: function(selectValues, triggerOnChange) {
         if (!$.isArray(selectValues)) {
            selectValues = [selectValues];
         }

         for (var i = 0; i < selectValues.length; i++) {
            var value = selectValues[i];

            if (value === null || value === undefined) {
               continue;
            }

            var $option = this.getOptionByValue(value);
            var $checkbox = this.getInputByValue(value);

            if ($option === undefined || $checkbox === undefined) {
               continue;
            }

            if (!this.options.multiple) {
               this.deselectAll(false);
            }

            if (this.options.selectedClass) {
               $checkbox.closest('li')
                  .addClass(this.options.selectedClass);
            }

            $checkbox.prop('checked', true);
            $option.prop('selected', true);
         }

         this.updateButtonText();
         this.updateSelectAll();

         if (triggerOnChange && selectValues.length === 1) {
            this.options.onChange($option, true);
         }
      },

      /**
       * Clears all selected items.
       */
      clearSelection: function() {
         this.deselectAll(false);
         this.updateButtonText();
         this.updateSelectAll();
      },

      /**
       * Deselects all options of the given values.
       *
       * If triggerOnChange is set to true, the on change event is triggered, if
       * and only if one value is passed.
       *
       * @param {Array} deselectValues
       * @param {Boolean} triggerOnChange
       */
      deselect: function(deselectValues, triggerOnChange) {
         if (!$.isArray(deselectValues)) {
            deselectValues = [deselectValues];
         }

         for (var i = 0; i < deselectValues.length; i++) {
            var value = deselectValues[i];

            if (value === null || value === undefined) {
               continue;
            }

            var $option = this.getOptionByValue(value);
            var $checkbox = this.getInputByValue(value);

            if ($option === undefined || $checkbox === undefined) {
               continue;
            }

            if (this.options.selectedClass) {
               $checkbox.closest('li')
                  .removeClass(this.options.selectedClass);
            }

            $checkbox.prop('checked', false);
            $option.prop('selected', false);
         }

         this.updateButtonText();
         this.updateSelectAll();

         if (triggerOnChange && deselectValues.length === 1) {
            this.options.onChange($option, false);
         }
      },

      /**
       * Selects all enabled & visible options.
       *
       * If justVisible is true or not specified, only visible options are selected.
       *
       * @param {Boolean} justVisible
       */
      selectAll: function(justVisible) {
         var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
         var allCheckboxes = $("li input[type='checkbox']:enabled", this.$ul);
         var visibleCheckboxes = allCheckboxes.filter(":visible");
         var allCheckboxesCount = allCheckboxes.length;
         var visibleCheckboxesCount = visibleCheckboxes.length;

         if (justVisible) {
            visibleCheckboxes.prop('checked', true);
            $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").addClass(this.options.selectedClass);
         } else {
            allCheckboxes.prop('checked', true);
            $("li:not(.divider):not(.disabled)", this.$ul).addClass(this.options.selectedClass);
         }

         if (allCheckboxesCount === visibleCheckboxesCount || justVisible === false) {
            $("option:enabled", this.$select).prop('selected', true);
         } else {
            var values = visibleCheckboxes.map(function() {
               return $(this).val();
            }).get();

            $("option:enabled", this.$select).filter(function(index) {
               return $.inArray($(this).val(), values) !== -1;
            }).prop('selected', true);
         }
      },

      /**
       * Deselects all options.
       *
       * If justVisible is true or not specified, only visible options are deselected.
       *
       * @param {Boolean} justVisible
       */
      deselectAll: function(justVisible) {
         var justVisible = typeof justVisible === 'undefined' ? true : justVisible;

         if (justVisible) {
            var visibleCheckboxes = $("li input[type='checkbox']:enabled", this.$ul).filter(":visible");
            visibleCheckboxes.prop('checked', false);

            var values = visibleCheckboxes.map(function() {
               return $(this).val();
            }).get();

            $("option:enabled", this.$select).filter(function(index) {
               return $.inArray($(this).val(), values) !== -1;
            }).prop('selected', false);

            if (this.options.selectedClass) {
               $("li:not(.divider):not(.disabled)", this.$ul).filter(":visible").removeClass(this.options.selectedClass);
            }
         } else {
            $("li input[type='checkbox']:enabled", this.$ul).prop('checked', false);
            $("option:enabled", this.$select).prop('selected', false);

            if (this.options.selectedClass) {
               $("li:not(.divider):not(.disabled)", this.$ul).removeClass(this.options.selectedClass);
            }
         }
      },

      /**
       * Rebuild the plugin.
       *
       * Rebuilds the dropdown, the filter and the select all option.
       */
      rebuild: function() {
         this.$ul.html('');

         // Important to distinguish between radios and checkboxes.
         this.options.multiple = this.$select.attr('multiple') === "multiple";

         this.buildSelectAll();
         this.buildDropdownOptions();
         this.buildFilter();

         this.updateButtonText();
         this.updateSelectAll();

         if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
            this.disable();
         }

         if (this.options.dropRight) {
            this.$ul.addClass('pull-right');
         }
      },

      /**
       * The provided data will be used to build the dropdown.
       */
      dataprovider: function(dataprovider) {
         var optionDOM = "";
         var groupCounter = 0;
         var tags = $(''); // create empty jQuery array

         $.each(dataprovider, function(index, option) {
            var tag;
            if ($.isArray(option.children)) { // create optiongroup tag
               groupCounter++;
               tag = $('<optgroup/>').attr({
                  label: option.label || 'Group ' + groupCounter
               });
               forEach(option.children, function(subOption) { // add children option tags
                  tag.append($('<option/>').attr({
                     value: subOption.value,
                     label: subOption.label || subOption.value,
                     title: subOption.title,
                     selected: !!subOption.selected
                  }));
               });

               optionDOM += '</optgroup>';
            } else { // create option tag
               tag = $('<option/>').attr({
                  value: option.value,
                  label: option.label || option.value,
                  title: option.title,
                  selected: !!option.selected
               });
            }

            tags = tags.add(tag);
         });

         this.$select.empty().append(tags);
         this.rebuild();
      },

      /**
       * Enable the multiselect.
       */
      enable: function() {
         this.$select.prop('disabled', false);
         this.$button.prop('disabled', false)
            .removeClass('disabled');
      },

      /**
       * Disable the multiselect.
       */
      disable: function() {
         this.$select.prop('disabled', true);
         this.$button.prop('disabled', true)
            .addClass('disabled');
      },

      /**
       * Set the options.
       *
       * @param {Array} options
       */
      setOptions: function(options) {
         this.options = this.mergeOptions(options);
      },

      /**
       * Merges the given options with the default options.
       *
       * @param {Array} options
       * @returns {Array}
       */
      mergeOptions: function(options) {
         return $.extend(true, {}, this.defaults, options);
      },

      /**
       * Checks whether a select all checkbox is present.
       *
       * @returns {Boolean}
       */
      hasSelectAll: function() {
         return $('li.' + this.options.selectAllValue, this.$ul).length > 0;
      },

      /**
       * Updates the select all checkbox based on the currently displayed and selected checkboxes.
       */
      updateSelectAll: function() {
         if (this.hasSelectAll()) {
            var allBoxes = $("li:not(.multiselect-item):not(.filter-hidden) input:enabled", this.$ul);
            var allBoxesLength = allBoxes.length;
            var checkedBoxesLength = allBoxes.filter(":checked").length;
            var selectAllLi = $("li." + this.options.selectAllValue, this.$ul);
            var selectAllInput = selectAllLi.find("input");

            if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
               selectAllInput.prop("checked", true);
               selectAllLi.addClass(this.options.selectedClass);
            } else {
               selectAllInput.prop("checked", false);
               selectAllLi.removeClass(this.options.selectedClass);
            }
         }
      },

      /**
       * Update the button text and its title based on the currently selected options.
       */
      updateButtonText: function() {
         var options = this.getSelected();

         // First update the displayed button text.
         $('.multiselect', this.$container).html(this.options.buttonText(options, this.$select));

         // Now update the title attribute of the button.
         $('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
      },

      /**
       * Get all selected options.
       *
       * @returns {jQUery}
       */
      getSelected: function() {
         return $('option', this.$select).filter(":selected");
      },

      /**
       * Gets a select option by its value.
       *
       * @param {String} value
       * @returns {jQuery}
       */
      getOptionByValue: function(value) {

         var options = $('option', this.$select);
         var valueToCompare = value.toString();

         for (var i = 0; i < options.length; i = i + 1) {
            var option = options[i];
            if (option.value === valueToCompare) {
               return $(option);
            }
         }
      },

      /**
       * Get the input (radio/checkbox) by its value.
       *
       * @param {String} value
       * @returns {jQuery}
       */
      getInputByValue: function(value) {

         var checkboxes = $('li input', this.$ul);
         var valueToCompare = value.toString();

         for (var i = 0; i < checkboxes.length; i = i + 1) {
            var checkbox = checkboxes[i];
            if (checkbox.value === valueToCompare) {
               return $(checkbox);
            }
         }
      },

      /**
       * Used for knockout integration.
       */
      updateOriginalOptions: function() {
         this.originalOptions = this.$select.clone()[0].options;
      },

      asyncFunction: function(callback, timeout, self) {
         var args = Array.prototype.slice.call(arguments, 3);
         return setTimeout(function() {
            callback.apply(self || window, args);
         }, timeout);
      }
   };

   $.fn.multiselect = function(option, parameter, extraOptions) {
      return this.each(function() {
         var data = $(this).data('multiselect');
         var options = typeof option === 'object' && option;

         // Initialize the multiselect.
         if (!data) {
            data = new Multiselect(this, options);
            $(this).data('multiselect', data);
         }

         // Call multiselect method.
         if (typeof option === 'string') {
            data[option](parameter, extraOptions);

            if (option === 'destroy') {
               $(this).data('multiselect', false);
            }
         }
      });
   };

   $.fn.multiselect.Constructor = Multiselect;

   $(function() {
      $("select[data-role=multiselect]").multiselect();
   });

}(window.jQuery);

