/**
 * angular-bootstrap-duallistbox
 * @version v0.1.0 - 2015-06-13
 * @author Francesco Pontillo (francescopontillo@gmail.com)
 * @link https://github.com/frapontillo/angular-bootstrap-duallistbox
 * @license Apache License 2.0
**/

'use strict';
// Source: common/module.js
angular.module('frapontillo.bootstrap-duallistbox', []);
// Source: dist/.temp/directives/bsDuallistbox.js
angular.module('frapontillo.bootstrap-duallistbox').directive('bsDuallistbox', [
  '$compile',
  '$timeout',
  function ($compile, $timeout) {
    return {
      restrict: 'A',
      require: 'ngModel',
      link: function link(scope, element, attrs) {
        //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888
        var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
        // The select collection
        var collection = attrs.ngOptions.match(NG_OPTIONS_REGEXP)[7];
        var getBooleanValue = function (attributeValue) {
          return attributeValue === true || attributeValue === 'true';
        };
        // The attribute names to $observe with related functions to call
        var attributes = {
            'bootstrap2': {
              changeFn: 'setBootstrap2Compatible',
              transformFn: getBooleanValue
            },
            'postfix': 'setHelperSelectNamePostfix',
            'selectMinHeight': {
              changeFn: 'setSelectOrMinimalHeight',
              defaultValue: 100
            },
            'filter': {
              changeFn: 'setShowFilterInputs',
              defaultValue: true,
              transformFn: getBooleanValue
            },
            'filterClear': {
              changeFn: 'setFilterTextClear',
              defaultValue: 'show all'
            },
            'filterPlaceholder': 'setFilterPlaceHolder',
            'filterValues': {
              changeFn: 'setFilterOnValues',
              transformFn: getBooleanValue
            },
            'moveOnSelect': {
              changeFn: 'setMoveOnSelect',
              defaultValue: true,
              transformFn: getBooleanValue
            },
            'preserveSelection': 'setPreserveSelectionOnMove',
            'moveSelectedLabel': 'setMoveSelectedLabel',
            'moveAllLabel': 'setMoveAllLabel',
            'removeSelectedLabel': 'setRemoveSelectedLabel',
            'removeAllLabel': 'setRemoveAllLabel',
            'selectedListLabel': 'setSelectedListLabel',
            'nonSelectedListLabel': 'setNonSelectedListLabel',
            'infoAll': {
              changeFn: 'setInfoText',
              defaultValue: 'Showing all {0}'
            },
            'infoFiltered': {
              changeFn: 'setInfoTextFiltered',
              defaultValue: '<span class="label label-warning">Filtered</span> {0} from {1}'
            },
            'infoEmpty': {
              changeFn: 'setInfoTextEmpty',
              defaultValue: 'Empty list'
            }
          };
        // The duallistbox element
        var dualListBox;
        /**
         * Calculates the proper attribute value, given its name.
         * The attribute value depends on:
         *   - the current value
         *   - the default value, if it exists
         *   - the transformFn, if it exists
         *
         * If the current value is undefined, it gets the default value.
         * The calculated value is then applied to the transformFn, if it is defined; the result is then returned.
         *
         * @param attributeName The {String} name of the attribute.
         * @returns {Object} representing the value of the attribute.
         */
        var getAttributeValueOrDefault = function (attributeName) {
          // get the attribute function/object for default and transformation
          var attributeFunction = attributes[attributeName];
          // get the current attribute value
          var attributeValue = attrs[attributeName];
          // By default, the default value and the transform function are not defined
          var defaultValue;
          var transformFn;
          // If the attributeFunction is an object
          if (angular.isObject(attributeFunction)) {
            // extract the default value and the transform function
            defaultValue = attributeFunction.defaultValue;
            transformFn = attributeFunction.transformFn;
          }
          // If the upcoming value is falsy, get the default
          if (!attributeValue) {
            attributeValue = defaultValue;
          }
          // If a transform function is defined, use it to change the value
          if (angular.isFunction(transformFn)) {
            attributeValue = transformFn(attributeValue);
          }
          return attributeValue;
        };
        /**
         * Gets the name of the function of `bootstrap-duallistbox` to be called to effectively
         * change the attribute in input.
         *
         * @param attributeName The name of the attribute to change.
         * @returns {String}, name of the `bootstrap-dual-listbox` to be called.
         */
        var getAttributeChangeFunction = function (attributeName) {
          // get the attribute function/object for default and transformation
          var attributeFunction = attributes[attributeName];
          // By default, attributeFunction is a function
          var actualFunction = attributeFunction;
          // If the attributeFunction is an object
          if (angular.isObject(attributeFunction)) {
            // extract the actual function name
            actualFunction = attributeFunction.changeFn;
          }
          return actualFunction;
        };
        /**
         * Listen to model changes.
         */
        var listenToModel = function () {
          // When ngModel changes, refresh the list
          // controller.$formatters.push(refresh);
          scope.$watch(attrs.ngModel, function () {
            initMaybe();
            refresh();
          });
          // When ngOptions changes, refresh the list
          scope.$watch(collection, refresh, true);
          // Watch for changes to the filter scope variables
          scope.$watch(attrs.filterNonSelected, function () {
            refresh();
          });
          scope.$watch(attrs.filterSelected, function () {
            refresh();
          });
          // $observe every attribute change
          angular.forEach(attributes, function (attributeFunction, attributeName) {
            attrs.$observe(attributeName, function () {
              var actualFunction = getAttributeChangeFunction(attributeName);
              var actualValue = getAttributeValueOrDefault(attributeName);
              // Depending on the attribute, call the right function (and always refresh)
              element.bootstrapDualListbox(actualFunction, actualValue, true);
            });
          });
        };
        /**
         * Refresh the Dual List Box using its own API.
         */
        var refresh = function () {
          // TODO: consider removing $timeout calls
          $timeout(function () {
            element.bootstrapDualListbox('refresh');
          });
        };
        /**
         * If the directive has not been initialized yet, do so.
         */
        var initMaybe = function () {
          // if it's the first initialization
          if (!dualListBox) {
            init();
          }
        };
        // Delay listbox init
        var init = function () {
          var defaults = {};
          // for every attribute the directive handles
          angular.forEach(attributes, function (attributeFunction, attributeName) {
            var actualValue = getAttributeValueOrDefault(attributeName);
            defaults[attributeName] = actualValue;
          });
          // Init the plugin
          dualListBox = element.bootstrapDualListbox({
            bootstrap2Compatible: defaults.bootstrap2,
            filterTextClear: defaults.filterClear,
            filterPlaceHolder: defaults.filterPlaceholder,
            moveSelectedLabel: defaults.moveSelectedLabel,
            moveAllLabel: defaults.moveAllLabel,
            removeSelectedLabel: defaults.removeSelectedLabel,
            removeAllLabel: defaults.removeAllLabel,
            moveOnSelect: defaults.moveOnSelect,
            preserveSelectionOnMove: defaults.preserveSelection,
            selectedListLabel: defaults.selectedListLabel,
            nonSelectedListLabel: defaults.nonSelectedListLabel,
            helperSelectNamePostfix: defaults.postfix,
            selectOrMinimalHeight: defaults.selectMinHeight,
            showFilterInputs: defaults.filter,
            nonSelectedFilter: '',
            selectedFilter: '',
            infoText: defaults.infoAll,
            infoTextFiltered: defaults.infoFiltered,
            infoTextEmpty: defaults.infoEmpty,
            filterOnValues: defaults.filterValues
          });
          // Inject the ng-model into the filters and re-compile them
          var container = element.bootstrapDualListbox('getContainer');
          var filterNonSelectedInput = container.find('.box1 .filter');
          filterNonSelectedInput.attr('ng-model', attrs.filterNonSelected);
          $compile(filterNonSelectedInput)(scope);
          var filterSelectedInput = container.find('.box2 .filter');
          filterSelectedInput.attr('ng-model', attrs.filterSelected);
          $compile(filterSelectedInput)(scope);
        };
        // Listen and respond to model changes
        listenToModel();
        // On destroy, collect ya garbage
        scope.$on('$destroy', function () {
          element.bootstrapDualListbox('destroy');
        });
      }
    };
  }
]);