text: 'use strict';
  
  var $animateMinErr = minErr('$animate');
  var ELEMENT_NODE = 1;
  var NG_ANIMATE_CLASSNAME = 'ng-animate';
  
  function mergeClasses(a,b) {
    if (!a && !b) return '';
    if (!a) return b;
    if (!b) return a;
    if (isArray(a)) a = a.join(' ');
    if (isArray(b)) b = b.join(' ');
    return a + ' ' + b;
  }
  
  function extractElementNode(element) {
    for (var i = 0; i < element.length; i++) {
      var elm = element[i];
      if (elm.nodeType === ELEMENT_NODE) {
        return elm;
      }
    }
  }
  
  function splitClasses(classes) {
    if (isString(classes)) {
      classes = classes.split(' ');
    }
  
    // Use createMap() to prevent class assumptions involving property names in
    // Object.prototype
    var obj = createMap();
    forEach(classes, function(klass) {
      // sometimes the split leaves empty string values
      // incase extra spaces were applied to the options
      if (klass.length) {
        obj[klass] = true;
      }
    });
    return obj;
  }
  
  // if any other type of options value besides an Object value is
  // passed into the $animate.method() animation then this helper code
  // will be run which will ignore it. While this patch is not the
  // greatest solution to this, a lot of existing plugins depend on
  // $animate to either call the callback (< 1.2) or return a promise
  // that can be changed. This helper function ensures that the options
  // are wiped clean incase a callback function is provided.
  function prepareAnimateOptions(options) {
    return isObject(options)
        ? options
        : {};
  }
  
  var $$CoreAnimateJsProvider = /** @this */ function() {
    this.$get = noop;
  };
  
  // this is prefixed with Core since it conflicts with
  // the animateQueueProvider defined in ngAnimate/animateQueue.js
  var $$CoreAnimateQueueProvider = /** @this */ function() {
    var postDigestQueue = new NgMap();
    var postDigestElements = [];
  
    this.$get = ['$$AnimateRunner', '$rootScope',
         function($$AnimateRunner,   $rootScope) {
      return {
        enabled: noop,
        on: noop,
        off: noop,
        pin: noop,
  
        push: function(element, event, options, domOperation) {
          if (domOperation) {
            domOperation();
          }
  
          options = options || {};
          if (options.from) {
            element.css(options.from);
          }
          if (options.to) {
            element.css(options.to);
          }
  
          if (options.addClass || options.removeClass) {
            addRemoveClassesPostDigest(element, options.addClass, options.removeClass);
          }
  
          var runner = new $$AnimateRunner();
  
          // since there are no animations to run the runner needs to be
          // notified that the animation call is complete.
          runner.complete();
          return runner;
        }
      };
  
  
      function updateData(data, classes, value) {
        var changed = false;
        if (classes) {
          classes = isString(classes) ? classes.split(' ') :
                    isArray(classes) ? classes : [];
          forEach(classes, function(className) {
            if (className) {
              changed = true;
              data[className] = value;
            }
          });
        }
        return changed;
      }
  
      function handleCSSClassChanges() {
        forEach(postDigestElements, function(element) {
          var data = postDigestQueue.get(element);
          if (data) {
            var existing = splitClasses(element.attr('class'));
            var toAdd = '';
            var toRemove = '';
            forEach(data, function(status, className) {
              var hasClass = !!existing[className];
              if (status !== hasClass) {
                if (status) {
                  toAdd += (toAdd.length ? ' ' : '') + className;
                } else {
                  toRemove += (toRemove.length ? ' ' : '') + className;
                }
              }
            });
  
            forEach(element, function(elm) {
              if (toAdd) {
                jqLiteAddClass(elm, toAdd);
              }
              if (toRemove) {
                jqLiteRemoveClass(elm, toRemove);
              }
            });
            postDigestQueue.delete(element);
          }
        });
        postDigestElements.length = 0;
      }
  
  
      function addRemoveClassesPostDigest(element, add, remove) {
        var data = postDigestQueue.get(element) || {};
  
        var classesAdded = updateData(data, add, true);
        var classesRemoved = updateData(data, remove, false);
  
        if (classesAdded || classesRemoved) {
  
          postDigestQueue.set(element, data);
          postDigestElements.push(element);
  
          if (postDigestElements.length === 1) {
            $rootScope.$$postDigest(handleCSSClassChanges);
          }
        }
      }
    }];
  };
  
  /**
   * @ngdoc provider
   * @name $animateProvider
   *
   * @description
   * Default implementation of $animate that doesn't perform any animations, instead just
   * synchronously performs DOM updates and resolves the returned runner promise.
   *
   * In order to enable animations the `ngAnimate` module has to be loaded.
   *
   * To see the functional implementation check out `src/ngAnimate/animate.js`.
   */
  var $AnimateProvider = ['$provide', /** @this */ function($provide) {
    var provider = this;
    var classNameFilter = null;
  
    this.$$registeredAnimations = Object.create(null);
  
     /**
     * @ngdoc method
     * @name $animateProvider#register
     *
     * @description
     * Registers a new injectable animation factory function. The factory function produces the
     * animation object which contains callback functions for each event that is expected to be
     * animated.
     *
     *   * `eventFn`: `function(element, ... , doneFunction, options)`
     *   The element to animate, the `doneFunction` and the options fed into the animation. Depending
     *   on the type of animation additional arguments will be injected into the animation function. The
     *   list below explains the function signatures for the different animation methods:
     *
     *   - setClass: function(element, addedClasses, removedClasses, doneFunction, options)
     *   - addClass: function(element, addedClasses, doneFunction, options)
     *   - removeClass: function(element, removedClasses, doneFunction, options)
     *   - enter, leave, move: function(element, doneFunction, options)
     *   - animate: function(element, fromStyles, toStyles, doneFunction, options)
     *
     *   Make sure to trigger the `doneFunction` once the animation is fully complete.
     *
     * ```js
     *   return {
     *     //enter, leave, move signature
     *     eventFn : function(element, done, options) {
     *       //code to run the animation
     *       //once complete, then run done()
     *       return function endFunction(wasCancelled) {
     *         //code to cancel the animation
     *       }
     *     }
     *   }
     * ```
     *
     * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to).
     * @param {Function} factory The factory function that will be executed to return the animation
     *                           object.
     */
    this.register = function(name, factory) {
      if (name && name.charAt(0) !== '.') {
        throw $animateMinErr('notcsel', 'Expecting class selector starting with \'.\' got \'{0}\'.', name);
      }
  
      var key = name + '-animation';
      provider.$$registeredAnimations[name.substr(1)] = key;
      $provide.factory(key, factory);
    };
  
    /**
     * @ngdoc method
     * @name $animateProvider#classNameFilter
     *
     * @description
     * Sets and/or returns the CSS class regular expression that is checked when performing
     * an animation. Upon bootstrap the classNameFilter value is not set at all and will
     * therefore enable $animate to attempt to perform an animation on any element that is triggered.
     * When setting the `classNameFilter` value, animations will only be performed on elements
     * that successfully match the filter expression. This in turn can boost performance
     * for low-powered devices as well as applications containing a lot of structural operations.
     * @param {RegExp=} expression The className expression which will be checked against all animations
     * @return {RegExp} The current CSS className expression value. If null then there is no expression value
     */
    this.classNameFilter = function(expression) {
      if (arguments.length === 1) {
        classNameFilter = (expression instanceof RegExp) ? expression : null;
        if (classNameFilter) {
          var reservedRegex = new RegExp('[(\\s|\\/)]' + NG_ANIMATE_CLASSNAME + '[(\\s|\\/)]');
          if (reservedRegex.test(classNameFilter.toString())) {
            classNameFilter = null;
            throw $animateMinErr('nongcls', '$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME);
          }
        }
      }
      return classNameFilter;
    };
  
    this.$get = ['$$animateQueue', function($$animateQueue) {
      function domInsert(element, parentElement, afterElement) {
        // if for some reason the previous element was removed
        // from the dom sometime before this code runs then let's
        // just stick to using the parent element as the anchor
        if (afterElement) {
          var afterNode = extractElementNode(afterElement);
          if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) {
            afterElement = null;
          }
        }
        if (afterElement) {
          afterElement.after(element);
        } else {
          parentElement.prepend(element);
        }
      }
  
      /**
       * @ngdoc service
       * @name $animate
       * @description The $animate service exposes a series of DOM utility methods that provide support
       * for animation hooks. The default behavior is the application of DOM operations, however,
       * when an animation is detected (and animations are enabled), $animate will do the heavy lifting
       * to ensure that animation runs with the triggered DOM operation.
       *
       * By default $animate doesn't trigger any animations. This is because the `ngAnimate` module isn't
       * included and only when it is active then the animation hooks that `$animate` triggers will be
       * functional. Once active then all structural `ng-` directives will trigger animations as they perform
       * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`,
       * `ngShow`, `ngHide` and `ngMessages` also provide support for animations.
       *
       * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives.
       *
       * To learn more about enabling animation support, click here to visit the
       * {@link ngAnimate ngAnimate module page}.
       */
      return {
        // we don't call it directly since non-existant arguments may
        // be interpreted as null within the sub enabled function
  
        /**
         *
         * @ngdoc method
         * @name $animate#on
         * @kind function
         * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...)
         *    has fired on the given element or among any of its children. Once the listener is fired, the provided callback
         *    is fired with the following params:
         *
         * ```js
         * $animate.on('enter', container,
         *    function callback(element, phase) {
         *      // cool we detected an enter animation within the container
         *    }
         * );
         * ```
         *
         * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...)
         * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself
         *     as well as among its children
         * @param {Function} callback the callback function that will be fired when the listener is triggered
         *
         * The arguments present in the callback function are:
         * * `element` - The captured DOM element that the animation was fired on.
         * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends).
         */
        on: $$animateQueue.on,
  
        /**
         *
         * @ngdoc method
         * @name $animate#off
         * @kind function
         * @description Deregisters an event listener based on the event which has been associated with the provided element. This method
         * can be used in three different ways depending on the arguments:
         *
         * ```js
         * // remove all the animation event listeners listening for `enter`
         * $animate.off('enter');
         *
         * // remove listeners for all animation events from the container element
         * $animate.off(container);
         *
         * // remove all the animation event listeners listening for `enter` on the given element and its children
         * $animate.off('enter', container);
         *
         * // remove the event listener function provided by `callback` that is set
         * // to listen for `enter` on the given `container` as well as its children
         * $animate.off('enter', container, callback);
         * ```
         *
         * @param {string|DOMElement} event|container the animation event (e.g. enter, leave, move,
         * addClass, removeClass, etc...), or the container element. If it is the element, all other
         * arguments are ignored.
         * @param {DOMElement=} container the container element the event listener was placed on
         * @param {Function=} callback the callback function that was registered as the listener
         */
        off: $$animateQueue.off,
  
        /**
         * @ngdoc method
         * @name $animate#pin
         * @kind function
         * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists
         *    outside of the DOM structure of the AngularJS application. By doing so, any animation triggered via `$animate` can be issued on the
         *    element despite being outside the realm of the application or within another application. Say for example if the application
         *    was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated
         *    as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind
         *    that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association.
         *
         *    Note that this feature is only active when the `ngAnimate` module is used.
         *
         * @param {DOMElement} element the external element that will be pinned
         * @param {DOMElement} parentElement the host parent element that will be associated with the external element
         */
        pin: $$animateQueue.pin,
  
        /**
         *
         * @ngdoc method
         * @name $animate#enabled
         * @kind function
         * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This
         * function can be called in four ways:
         *
         * ```js
         * // returns true or false
         * $animate.enabled();
         *
         * // changes the enabled state for all animations
         * $animate.enabled(false);
         * $animate.enabled(true);
         *
         * // returns true or false if animations are enabled for an element
         * $animate.enabled(element);
         *
         * // changes the enabled state for an element and its children
         * $animate.enabled(element, true);
         * $animate.enabled(element, false);
         * ```
         *
         * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state
         * @param {boolean=} enabled whether or not the animations will be enabled for the element
         *
         * @return {boolean} whether or not animations are enabled
         */
        enabled: $$animateQueue.enabled,
  
        /**
         * @ngdoc method
         * @name $animate#cancel
         * @kind function
         * @description Cancels the provided animation.
         *
         * @param {Promise} animationPromise The animation promise that is returned when an animation is started.
         */
        cancel: function(runner) {
          if (runner.end) {
            runner.end();
          }
        },
  
        /**
         *
         * @ngdoc method
         * @name $animate#enter
         * @kind function
         * @description Inserts the element into the DOM either after the `after` element (if provided) or
         *   as the first child within the `parent` element and then triggers an animation.
         *   A promise is returned that will be resolved during the next digest once the animation
         *   has completed.
         *
         * @param {DOMElement} element the element which will be inserted into the DOM
         * @param {DOMElement} parent the parent element which will append the element as
         *   a child (so long as the after element is not present)
         * @param {DOMElement=} after the sibling element after which the element will be appended
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        enter: function(element, parent, after, options) {
          parent = parent && jqLite(parent);
          after = after && jqLite(after);
          parent = parent || after.parent();
          domInsert(element, parent, after);
          return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options));
        },
  
        /**
         *
         * @ngdoc method
         * @name $animate#move
         * @kind function
         * @description Inserts (moves) the element into its new position in the DOM either after
         *   the `after` element (if provided) or as the first child within the `parent` element
         *   and then triggers an animation. A promise is returned that will be resolved
         *   during the next digest once the animation has completed.
         *
         * @param {DOMElement} element the element which will be moved into the new DOM position
         * @param {DOMElement} parent the parent element which will append the element as
         *   a child (so long as the after element is not present)
         * @param {DOMElement=} after the sibling element after which the element will be appended
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        move: function(element, parent, after, options) {
          parent = parent && jqLite(parent);
          after = after && jqLite(after);
          parent = parent || after.parent();
          domInsert(element, parent, after);
          return $$animateQueue.push(element, 'move', prepareAnimateOptions(options));
        },
  
        /**
         * @ngdoc method
         * @name $animate#leave
         * @kind function
         * @description Triggers an animation and then removes the element from the DOM.
         * When the function is called a promise is returned that will be resolved during the next
         * digest once the animation has completed.
         *
         * @param {DOMElement} element the element which will be removed from the DOM
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        leave: function(element, options) {
          return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() {
            element.remove();
          });
        },
  
        /**
         * @ngdoc method
         * @name $animate#addClass
         * @kind function
         *
         * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon
         *   execution, the addClass operation will only be handled after the next digest and it will not trigger an
         *   animation if element already contains the CSS class or if the class is removed at a later step.
         *   Note that class-based animations are treated differently compared to structural animations
         *   (like enter, move and leave) since the CSS classes may be added/removed at different points
         *   depending if CSS or JavaScript animations are used.
         *
         * @param {DOMElement} element the element which the CSS classes will be applied to
         * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces)
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        addClass: function(element, className, options) {
          options = prepareAnimateOptions(options);
          options.addClass = mergeClasses(options.addclass, className);
          return $$animateQueue.push(element, 'addClass', options);
        },
  
        /**
         * @ngdoc method
         * @name $animate#removeClass
         * @kind function
         *
         * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon
         *   execution, the removeClass operation will only be handled after the next digest and it will not trigger an
         *   animation if element does not contain the CSS class or if the class is added at a later step.
         *   Note that class-based animations are treated differently compared to structural animations
         *   (like enter, move and leave) since the CSS classes may be added/removed at different points
         *   depending if CSS or JavaScript animations are used.
         *
         * @param {DOMElement} element the element which the CSS classes will be applied to
         * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces)
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        removeClass: function(element, className, options) {
          options = prepareAnimateOptions(options);
          options.removeClass = mergeClasses(options.removeClass, className);
          return $$animateQueue.push(element, 'removeClass', options);
        },
  
        /**
         * @ngdoc method
         * @name $animate#setClass
         * @kind function
         *
         * @description Performs both the addition and removal of a CSS classes on an element and (during the process)
         *    triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and
         *    `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has
         *    passed. Note that class-based animations are treated differently compared to structural animations
         *    (like enter, move and leave) since the CSS classes may be added/removed at different points
         *    depending if CSS or JavaScript animations are used.
         *
         * @param {DOMElement} element the element which the CSS classes will be applied to
         * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces)
         * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces)
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        setClass: function(element, add, remove, options) {
          options = prepareAnimateOptions(options);
          options.addClass = mergeClasses(options.addClass, add);
          options.removeClass = mergeClasses(options.removeClass, remove);
          return $$animateQueue.push(element, 'setClass', options);
        },
  
        /**
         * @ngdoc method
         * @name $animate#animate
         * @kind function
         *
         * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element.
         * If any detected CSS transition, keyframe or JavaScript matches the provided className value, then the animation will take
         * on the provided styles. For example, if a transition animation is set for the given className, then the provided `from` and
         * `to` styles will be applied alongside the given transition. If the CSS style provided in `from` does not have a corresponding
         * style in `to`, the style in `from` is applied immediately, and no animation is run.
         * If a JavaScript animation is detected then the provided styles will be given in as function parameters into the `animate`
         * method (or as part of the `options` parameter):
         *
         * ```js
         * ngModule.animation('.my-inline-animation', function() {
         *   return {
         *     animate : function(element, from, to, done, options) {
         *       //animation
         *       done();
         *     }
         *   }
         * });
         * ```
         *
         * @param {DOMElement} element the element which the CSS styles will be applied to
         * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation.
         * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation.
         * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If
         *    this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element.
         *    (Note that if no animation is detected then this value will not be applied to the element.)
         * @param {object=} options an optional collection of options/styles that will be applied to the element.
         *   The object can have the following properties:
         *
         *   - **addClass** - `{string}` - space-separated CSS classes to add to element
         *   - **from** - `{Object}` - CSS properties & values at the beginning of animation. Must have matching `to`
         *   - **removeClass** - `{string}` - space-separated CSS classes to remove from element
         *   - **to** - `{Object}` - CSS properties & values at end of animation. Must have matching `from`
         *
         * @return {Promise} the animation callback promise
         */
        animate: function(element, from, to, className, options) {
          options = prepareAnimateOptions(options);
          options.from = options.from ? extend(options.from, from) : from;
          options.to   = options.to   ? extend(options.to, to)     : to;
  
          className = className || 'ng-inline-animate';
          options.tempClasses = mergeClasses(options.tempClasses, className);
          return $$animateQueue.push(element, 'animate', options);
        }
      };
    }];
  }];
  