'use strict';
/**
 * @ngdoc function
 * @name angular.module.ng.$compile
 * @function
 *
 * @description
 * Compiles a piece of HTML string or DOM into a template and produces a template function, which
 * can then be used to link {@link angular.module.ng.$rootScope.Scope scope} and the template together.
 *
 * The compilation is a process of walking the DOM tree and trying to match DOM elements to
 * {@link angular.module.ng.$compileProvider.directive directives}. For each match it
 * executes corresponding template function and collects the
 * instance functions into a single template function which is then returned.
 *
 * The template function can then be used once to produce the view or as it is the case with
 * {@link angular.module.ng.$compileProvider.directive.ng:repeat repeater} many-times, in which
 * case each call results in a view that is a DOM clone of the original template.
 *
 
   
    
    
   
   
     it('should auto compile', function() {
       expect(element('div[compile]').text()).toBe('Hello Angular');
       input('html').enter('{{name}}!');
       expect(element('div[compile]').text()).toBe('Angular!');
     });
   
 
 *
 *
 * @param {string|DOMElement} element Element or HTML string to compile into a template function.
 * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
 * (a DOM element/tree) to a scope. Where:
 *
 *  * `scope` - A {@link angular.module.ng.$rootScope.Scope Scope} to bind to.
 *  * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
 *               `template` and call the `cloneAttachFn` function allowing the caller to attach the
 *               cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
 *               called as: 
 `cloneAttachFn(clonedElement, scope)` where:
 *
 *      * `clonedElement` - is a clone of the original `element` passed into the compiler.
 *      * `scope` - is the current scope with which the linking function is working with.
 *
 * Calling the linking function returns the element of the template. It is either the original element
 * passed in, or the clone of the element if the `cloneAttachFn` is provided.
 *
 * After linking the view is not updateh until after a call to $digest which typically is done by
 * Angular automatically.
 *
 * If you need access to the bound view, there are two ways to do it:
 *
 * - If you are not asking the linking function to clone the template, create the DOM element(s)
 *   before you send them to the compiler and keep this reference around.
 *   
 *     var element = $compile('{{total}}
')(scope);
 *   
 *
 * - if on the other hand, you need the element to be cloned, the view reference from the original
 *   example would not point to the clone, but rather to the original template that was cloned. In
 *   this case, you can access the clone via the cloneAttachFn:
 *   
 *     var templateHTML = angular.element('{{total}}
'),
 *         scope = ....;
 *
 *     var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
 *       //attach the clone to DOM document at the right place
 *     });
 *
 *     //now we have reference to the cloned DOM via `clone`
 *   
 *
 *
 * Compiler Methods For Widgets and Directives:
 *
 * The following methods are available for use when you write your own widgets, directives,
 * and markup.
 *
 *
 * For information on how the compiler works, see the
 * {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
 */
$CompileProvider.$inject = ['$provide'];
function $CompileProvider($provide) {
  var hasDirectives = {},
      Suffix = 'Directive',
      COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
      CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
      CONTENT_REGEXP = /\<\\>/i,
      HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/,
      SIDE_EFFECT_ATTRS = {};
  forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) {
    SIDE_EFFECT_ATTRS[name] = name;
    SIDE_EFFECT_ATTRS[directiveNormalize('ng_' + name)] = name;
  });
  this.directive = function registerDirective(name, directiveFactory) {
    if (isString(name)) {
      assertArg(directiveFactory, 'directive');
      if (!hasDirectives.hasOwnProperty(name)) {
        hasDirectives[name] = [];
        $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
          function($injector, $exceptionHandler) {
            var directives = [];
            forEach(hasDirectives[name], function(directiveFactory) {
              try {
                var directive = $injector.invoke(directiveFactory);
                if (isFunction(directive)) {
                  directive = { compile: valueFn(directive) };
                } else if (!directive.compile && directive.link) {
                  directive.compile = valueFn(directive.link);
                }
                directive.priority = directive.priority || 0;
                directive.name = name;
                directive.restrict = directive.restrict || 'EACM';
                directives.push(directive);
              } catch (e) {
                $exceptionHandler(e);
              }
            });
            return directives;
          }]);
      }
      hasDirectives[name].push(directiveFactory);
    } else {
      forEach(name, reverseParams(registerDirective));
    }
    return this;
  };
  this.$get = ['$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache',
       function($injector,   $interpolate,   $exceptionHandler,   $http,   $templateCache) {
    return function(templateElement) {
      templateElement = jqLite(templateElement);
      var linkingFn = compileNodes(templateElement, templateElement);
      return function(scope, cloneConnectFn){
        assertArg(scope, 'scope');
        // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
        // and sometimes changes the structure of the DOM.
        var element = cloneConnectFn
          ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
          : templateElement;
        element.data('$scope', scope);
        if (cloneConnectFn) cloneConnectFn(element, scope);
        if (linkingFn) linkingFn(scope, element, element);
        return element;
      };
    };
    //================================
    /**
     * Compile function matches each node in nodeList against the directives. Once all directives
     * for a particular node are collected their compile functions are executed. The compile
     * functions return values - the linking functions - are combined into a composite linking
     * function, which is the a linking function for the node.
     *
     * @param {NodeList} nodeList an array of nodes to compile
     * @param {DOMElement=} rootElement If the nodeList is the root of the compilation tree then the
     *        rootElement must be set the jqLite collection of the compile root. This is
     *        needed so that the jqLite collection items can be replaced with widgets.
     * @returns {?function} A composite linking function of all of the matched directives or null.
     */
    function compileNodes(nodeList, rootElement) {
     var linkingFns = [],
         directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
     for(var i = 0, ii = nodeList.length; i < ii; i++) {
       attrs = {
         $attr: {},
         $normalize: directiveNormalize,
         $set: attrSetter
       };
       // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
       directives = collectDirectives(nodeList[i], [], attrs);
       directiveLinkingFn = (directives.length)
           ? applyDirectivesToNode(directives, nodeList[i], attrs, rootElement)
           : null;
       childLinkingFn = (directiveLinkingFn && directiveLinkingFn.terminal)
           ? null
           : compileNodes(nodeList[i].childNodes);
       linkingFns.push(directiveLinkingFn);
       linkingFns.push(childLinkingFn);
       linkingFnFound = (linkingFnFound || directiveLinkingFn || childLinkingFn);
     }
     // return a linking function if we have found anything, null otherwise
     return linkingFnFound ? linkingFn : null;
     function linkingFn(scope, nodeList, rootElement) {
       if (linkingFns.length != nodeList.length * 2) {
         throw Error('Template changed structure!');
       }
       var childLinkingFn, directiveLinkingFn, node, childScope;
       for(var i=0, n=0, ii=linkingFns.length; i
          addDirective(directives, directiveNormalize(nodeName_(node).toLowerCase()), 'E');
          // iterate over the attributes
          for (var attr, name, nName, value, nAttrs = node.attributes,
                   j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
            attr = nAttrs[j];
            name = attr.name;
            nName = directiveNormalize(name.toLowerCase());
            attrsMap[nName] = name;
            attrs[nName] = value = trim((msie && name == 'href')
                ? decodeURIComponent(node.getAttribute(name, 2))
                : attr.value);
            if (BOOLEAN_ATTR[nName]) {
              attrs[nName] = true; // presence means true
            }
            addAttrInterpolateDirective(directives, value, nName);
            addDirective(directives, nName, 'A');
          }
          // use class as directive
          className = node.className;
          while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
            nName = directiveNormalize(match[2]);
            if (addDirective(directives, nName, 'C')) {
              attrs[nName] = trim(match[3]);
            }
            className = className.substr(match.index + match[0].length);
          }
          break;
        case 3: /* Text Node */
          addTextInterpolateDirective(directives, node.nodeValue);
          break;
        case 8: /* Comment */
          match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
          if (match) {
            nName = directiveNormalize(match[1]);
            if (addDirective(directives, nName, 'M')) {
              attrs[nName] = trim(match[2]);
            }
          }
          break;
      }
      directives.sort(byPriority);
      return directives;
    }
    /**
     * Once the directives have been collected their compile functions is executed. This method
     * is responsible for inlining widget templates as well as terminating the application
     * of the directives if the terminal directive has been reached..
     *
     * @param {Array} directives Array of collected directives to execute their compile function.
     *        this needs to be pre-sorted by priority order.
     * @param {Node} templateNode The raw DOM node to apply the compile functions to
     * @param {Object} templateAttrs The shared attribute function
     * @param {DOMElement} rootElement If we are working on the root of the compile tree then this
     *        argument has the root jqLite array so that we can replace widgets on it.
     * @returns linkingFn
     */
    function applyDirectivesToNode(directives, templateNode, templateAttrs, rootElement) {
      var terminalPriority = -Number.MAX_VALUE,
          preLinkingFns = [],
          postLinkingFns = [],
          newScopeDirective = null,
          templateDirective = null,
          delayedLinkingFn = null,
          element = templateAttrs.$element = jqLite(templateNode),
          directive, linkingFn;
      // executes all directives on the current element
      for(var i = 0, ii = directives.length; i < ii; i++) {
        directive = directives[i];
        if (terminalPriority > directive.priority) {
          break; // prevent further processing of directives
        }
        if (directive.scope) {
          assertNoDuplicate('new scope', newScopeDirective, directive, element);
          newScopeDirective = directive;
        }
        if (directive.template) {
          assertNoDuplicate('template', templateDirective, directive, element);
          templateDirective = directive;
          // include the contents of the original element into the template and replace the element
          var content = directive.template.replace(CONTENT_REGEXP, element.html());
          templateNode = jqLite(content)[0];
          if (directive.replace) {
            replaceWith(rootElement, element, templateNode);
            var newTemplateAttrs = {$attr: {}};
            // combine directives from the original node and from the template:
            // - take the array of directives for this element
            // - split it into two parts, those that were already applied and those that weren't
            // - collect directives from the template, add them to the second group and sort them
            // - append the second group with new directives to the first group
            directives = directives.concat(
                collectDirectives(
                    templateNode,
                    directives.splice(i + 1, directives.length - (i + 1)),
                    newTemplateAttrs
                )
            );
            mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
            ii = directives.length;
          } else {
            element.html(content);
          }
        }
        if (directive.templateUrl) {
          assertNoDuplicate('template', templateDirective, directive, element);
          templateDirective = directive;
          delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i),
              compositeLinkFn, element, templateAttrs, rootElement, directive.replace);
          ii = directives.length;
        } else if (directive.compile) {
          try {
            linkingFn = directive.compile(element, templateAttrs);
            if (isFunction(linkingFn)) {
              postLinkingFns.push(linkingFn);
            } else if (linkingFn) {
              if (linkingFn.pre) preLinkingFns.push(linkingFn.pre);
              if (linkingFn.post) postLinkingFns.push(linkingFn.post);
            }
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }
        if (directive.terminal) {
          compositeLinkFn.terminal = true;
          terminalPriority = Math.max(terminalPriority, directive.priority);
        }
      }
      compositeLinkFn.scope = !!newScopeDirective;
      // if we have templateUrl, then we have to delay linking
      return delayedLinkingFn || compositeLinkFn;
      ////////////////////
      function compositeLinkFn(childLinkingFn, scope, linkNode) {
        var attrs, element, i, ii;
        if (templateNode === linkNode) {
          attrs = templateAttrs;
        } else {
          attrs = shallowCopy(templateAttrs);
          attrs.$element = jqLite(linkNode);
        }
        element = attrs.$element;
        // PRELINKING
        for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
          try {
            preLinkingFns[i](scope, element, attrs);
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }
        // RECURSION
        childLinkingFn && childLinkingFn(scope, linkNode.childNodes);
        // POSTLINKING
        for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
          try {
            postLinkingFns[i](scope, element, attrs);
          } catch (e) {
            $exceptionHandler(e, startingTag(element));
          }
        }
      }
    }
    /**
     * looks up the directive and decorates it with exception handling and proper parameters. We
     * call this the boundDirective.
     *
     * @param {string} name name of the directive to look up.
     * @param {string} location The directive must be found in specific format.
     *   String containing any of theses characters:
     *
     *   * `E`: element name
     *   * `A': attribute
     *   * `C`: class
     *   * `M`: comment
     * @returns true if directive was added.
     */
    function addDirective(tDirectives, name, location) {
      var match = false;
      if (hasDirectives.hasOwnProperty(name)) {
        for(var directive, directives = $injector.get(name + Suffix),
            i=0, ii = directives.length; i ignore
        return;
      }
      directives.push({
        priority: 100,
        compile: function(element, attr) {
          if (interpolateFn) {
            return function(scope, element, attr) {
              scope.$watch(interpolateFn, function(value) {
                attr.$set(name, value);
              });
            };
          } else {
            attr.$set(name, value);
          }
        }
      });
    }
    /**
     * This is a special jqLite.replaceWith, which can replace items which
     * have no parents, provided that the containing jqLite collection is provided.
     *
     * @param {JqLite=} rootElement The root of the compile tree. Used so that we can replace nodes
     *    in the root of the tree.
     * @param {JqLite} element The jqLite element which we are going to replace. We keep the shell,
     *    but replace its DOM node reference.
     * @param {Node} newNode The new DOM node.
     */
    function replaceWith(rootElement, element, newNode) {
      var oldNode = element[0],
          parent = oldNode.parentNode,
          i, ii;
      if (rootElement) {
        for(i = 0, ii = rootElement.length; i