define([

  'jquery',

  'require',



  './results',



  './selection/single',

  './selection/multiple',

  './selection/placeholder',

  './selection/allowClear',

  './selection/search',

  './selection/eventRelay',



  './utils',

  './translation',

  './diacritics',



  './data/select',

  './data/array',

  './data/ajax',

  './data/tags',

  './data/tokenizer',

  './data/minimumInputLength',

  './data/maximumInputLength',

  './data/maximumSelectionLength',



  './dropdown',

  './dropdown/search',

  './dropdown/hidePlaceholder',

  './dropdown/infiniteScroll',

  './dropdown/attachBody',

  './dropdown/minimumResultsForSearch',

  './dropdown/selectOnClose',

  './dropdown/closeOnSelect',



  './i18n/en'

], function ($, require,



             ResultsList,



             SingleSelection, MultipleSelection, Placeholder, AllowClear,

             SelectionSearch, EventRelay,



             Utils, Translation, DIACRITICS,



             SelectData, ArrayData, AjaxData, Tags, Tokenizer,

             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,



             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,

             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,



             EnglishTranslation) {

  function Defaults () {

    this.reset();

  }



  Defaults.prototype.apply = function (options) {

    options = $.extend(true, {}, this.defaults, options);



    if (options.dataAdapter == null) {

      if (options.ajax != null) {

        options.dataAdapter = AjaxData;

      } else if (options.data != null) {

        options.dataAdapter = ArrayData;

      } else {

        options.dataAdapter = SelectData;

      }



      if (options.minimumInputLength > 0) {

        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          MinimumInputLength

        );

      }



      if (options.maximumInputLength > 0) {

        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          MaximumInputLength

        );

      }



      if (options.maximumSelectionLength > 0) {

        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          MaximumSelectionLength

        );

      }



      if (options.tags) {

        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);

      }



      if (options.tokenSeparators != null || options.tokenizer != null) {

        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          Tokenizer

        );

      }



      if (options.query != null) {

        var Query = require(options.amdBase + 'compat/query');



        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          Query

        );

      }



      if (options.initSelection != null) {

        var InitSelection = require(options.amdBase + 'compat/initSelection');



        options.dataAdapter = Utils.Decorate(

          options.dataAdapter,

          InitSelection

        );

      }

    }



    if (options.resultsAdapter == null) {

      options.resultsAdapter = ResultsList;



      if (options.ajax != null) {

        options.resultsAdapter = Utils.Decorate(

          options.resultsAdapter,

          InfiniteScroll

        );

      }



      if (options.placeholder != null) {

        options.resultsAdapter = Utils.Decorate(

          options.resultsAdapter,

          HidePlaceholder

        );

      }



      if (options.selectOnClose) {

        options.resultsAdapter = Utils.Decorate(

          options.resultsAdapter,

          SelectOnClose

        );

      }

    }



    if (options.dropdownAdapter == null) {

      if (options.multiple) {

        options.dropdownAdapter = Dropdown;

      } else {

        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);



        options.dropdownAdapter = SearchableDropdown;

      }



      if (options.minimumResultsForSearch !== 0) {

        options.dropdownAdapter = Utils.Decorate(

          options.dropdownAdapter,

          MinimumResultsForSearch

        );

      }



      if (options.closeOnSelect) {

        options.dropdownAdapter = Utils.Decorate(

          options.dropdownAdapter,

          CloseOnSelect

        );

      }



      if (

        options.dropdownCssClass != null ||

        options.dropdownCss != null ||

        options.adaptDropdownCssClass != null

      ) {

        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');



        options.dropdownAdapter = Utils.Decorate(

          options.dropdownAdapter,

          DropdownCSS

        );

      }



      options.dropdownAdapter = Utils.Decorate(

        options.dropdownAdapter,

        AttachBody

      );

    }



    if (options.selectionAdapter == null) {

      if (options.multiple) {

        options.selectionAdapter = MultipleSelection;

      } else {

        options.selectionAdapter = SingleSelection;

      }



      // Add the placeholder mixin if a placeholder was specified

      if (options.placeholder != null) {

        options.selectionAdapter = Utils.Decorate(

          options.selectionAdapter,

          Placeholder

        );

      }



      if (options.allowClear) {

        options.selectionAdapter = Utils.Decorate(

          options.selectionAdapter,

          AllowClear

        );

      }



      if (options.multiple) {

        options.selectionAdapter = Utils.Decorate(

          options.selectionAdapter,

          SelectionSearch

        );

      }



      if (

        options.containerCssClass != null ||

        options.containerCss != null ||

        options.adaptContainerCssClass != null

      ) {

        var ContainerCSS = require(options.amdBase + 'compat/containerCss');



        options.selectionAdapter = Utils.Decorate(

          options.selectionAdapter,

          ContainerCSS

        );

      }



      options.selectionAdapter = Utils.Decorate(

        options.selectionAdapter,

        EventRelay

      );

    }



    if (typeof options.language === 'string') {

      // Check if the language is specified with a region

      if (options.language.indexOf('-') > 0) {

        // Extract the region information if it is included

        var languageParts = options.language.split('-');

        var baseLanguage = languageParts[0];



        options.language = [options.language, baseLanguage];

      } else {

        options.language = [options.language];

      }

    }



    if ($.isArray(options.language)) {

      var languages = new Translation();

      options.language.push('en');



      var languageNames = options.language;



      for (var l = 0; l < languageNames.length; l++) {

        var name = languageNames[l];

        var language = {};



        try {

          // Try to load it with the original name

          language = Translation.loadPath(name);

        } catch (e) {

          try {

            // If we couldn't load it, check if it wasn't the full path

            name = this.defaults.amdLanguageBase + name;

            language = Translation.loadPath(name);

          } catch (ex) {

            // The translation could not be loaded at all. Sometimes this is

            // because of a configuration problem, other times this can be

            // because of how Select2 helps load all possible translation files.

            if (options.debug && window.console && console.warn) {

              console.warn(

                'Select2: The language file for "' + name + '" could not be ' +

                'automatically loaded. A fallback will be used instead.'

              );

            }



            continue;

          }

        }



        languages.extend(language);

      }



      options.translations = languages;

    } else {

      var baseTranslation = Translation.loadPath(

        this.defaults.amdLanguageBase + 'en'

      );

      var customTranslation = new Translation(options.language);



      customTranslation.extend(baseTranslation);



      options.translations = customTranslation;

    }



    return options;

  };



  Defaults.prototype.reset = function () {

    function stripDiacritics (text) {

      // Used 'uni range + named function' from http://jsperf.com/diacritics/18

      function match(a) {

        return DIACRITICS[a] || a;

      }



      return text.replace(/[^\u0000-\u007E]/g, match);

    }



    function matcher (params, data) {

      // Always return the object if there is nothing to compare

      if ($.trim(params.term) === '') {

        return data;

      }



      // Do a recursive check for options with children

      if (data.children && data.children.length > 0) {

        // Clone the data object if there are children

        // This is required as we modify the object to remove any non-matches

        var match = $.extend(true, {}, data);



        // Check each child of the option

        for (var c = data.children.length - 1; c >= 0; c--) {

          var child = data.children[c];



          var matches = matcher(params, child);



          // If there wasn't a match, remove the object in the array

          if (matches == null) {

            match.children.splice(c, 1);

          }

        }



        // If any children matched, return the new object

        if (match.children.length > 0) {

          return match;

        }



        // If there were no matching children, check just the plain object

        return matcher(params, match);

      }



      var original = stripDiacritics(data.text).toUpperCase();

      var term = stripDiacritics(params.term).toUpperCase();



      // Check if the text contains the term

      if (original.indexOf(term) > -1) {

        return data;

      }



      // If it doesn't contain the term, don't return anything

      return null;

    }



    this.defaults = {

      amdBase: './',

      amdLanguageBase: './i18n/',

      closeOnSelect: true,

      debug: false,

      dropdownAutoWidth: false,

      escapeMarkup: Utils.escapeMarkup,

      language: EnglishTranslation,

      matcher: matcher,

      minimumInputLength: 0,

      maximumInputLength: 0,

      maximumSelectionLength: 0,

      minimumResultsForSearch: 0,

      selectOnClose: false,

      sorter: function (data) {

        return data;

      },

      templateResult: function (result) {

        return result.text;

      },

      templateSelection: function (selection) {

        return selection.text;

      },

      theme: 'default',

      width: 'resolve'

    };

  };



  Defaults.prototype.set = function (key, value) {

    var camelKey = $.camelCase(key);



    var data = {};

    data[camelKey] = value;



    var convertedData = Utils._convertData(data);



    $.extend(this.defaults, convertedData);

  };



  var defaults = new Defaults();



  return defaults;

});

