text: 'use strict';
  
  /* global -nullFormCtrl, -SUBMITTED_CLASS */
  var nullFormCtrl = {
    $addControl: noop,
    $removeControl: noop,
    $setValidity: noop,
    $$setPending: noop,
    $setDirty: noop,
    $setPristine: noop,
    $setSubmitted: noop,
    $$clearControlValidity: noop
  },
  SUBMITTED_CLASS = 'ng-submitted';
  
  /**
   * @ngdoc type
   * @name form.FormController
   *
   * @property {boolean} $pristine True if user has not interacted with the form yet.
   * @property {boolean} $dirty True if user has already interacted with the form.
   * @property {boolean} $valid True if all of the containing forms and controls are valid.
   * @property {boolean} $invalid True if at least one containing control or form is invalid.
   *
   * @property {Object} $error Is an object hash, containing references to all invalid controls or
   *  forms, where:
   *
   *  - keys are validation tokens (error names),
   *  - values are arrays of controls or forms that are invalid for given error name.
   *
   *
   *  Built-in validation tokens:
   *
   *  - `email`
   *  - `max`
   *  - `maxlength`
   *  - `min`
   *  - `minlength`
   *  - `number`
   *  - `pattern`
   *  - `required`
   *  - `url`
   *
   * @description
   * `FormController` keeps track of all its controls and nested forms as well as the state of them,
   * such as being valid/invalid or dirty/pristine.
   *
   * Each {@link ng.directive:form form} directive creates an instance
   * of `FormController`.
   *
   */
  //asks for $scope to fool the BC controller module
  FormController.$inject = ['$element', '$attrs', '$scope', '$animate'];
  function FormController(element, attrs, $scope, $animate) {
    var form = this,
        parentForm = element.parent().controller('form') || nullFormCtrl,
        invalidCount = 0, // used to easily determine if we are valid
        pendingCount = 0,
        controls = [],
        errors = form.$error = {};
  
    // init state
    form.$name = attrs.name || attrs.ngForm;
    form.$dirty = false;
    form.$pristine = true;
    form.$valid = true;
    form.$invalid = false;
    form.$submitted = false;
  
    parentForm.$addControl(form);
  
    // Setup initial state of the control
    element.addClass(PRISTINE_CLASS);
    toggleValidCss(true);
  
    // convenience method for easy toggling of classes
    function toggleValidCss(isValid, validationErrorKey) {
      validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
      $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey);
      $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
    }
  
    /**
     * @ngdoc method
     * @name form.FormController#$rollbackViewValue
     *
     * @description
     * Rollback all form controls pending updates to the `$modelValue`.
     *
     * Updates may be pending by a debounced event or because the input is waiting for a some future
     * event defined in `ng-model-options`. This method is typically needed by the reset button of
     * a form that uses `ng-model-options` to pend updates.
     */
    form.$rollbackViewValue = function() {
      forEach(controls, function(control) {
        control.$rollbackViewValue();
      });
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$commitViewValue
     *
     * @description
     * Commit all form controls pending updates to the `$modelValue`.
     *
     * Updates may be pending by a debounced event or because the input is waiting for a some future
     * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
     * usually handles calling this in response to input events.
     */
    form.$commitViewValue = function() {
      forEach(controls, function(control) {
        control.$commitViewValue();
      });
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$addControl
     *
     * @description
     * Register a control with the form.
     *
     * Input elements using ngModelController do this automatically when they are linked.
     */
    form.$addControl = function(control) {
      // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored
      // and not added to the scope.  Now we throw an error.
      assertNotHasOwnProperty(control.$name, 'input');
      controls.push(control);
  
      if (control.$name) {
        form[control.$name] = control;
      }
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$removeControl
     *
     * @description
     * Deregister a control from the form.
     *
     * Input elements using ngModelController do this automatically when they are destroyed.
     */
    form.$removeControl = function(control) {
      if (control.$name && form[control.$name] === control) {
        delete form[control.$name];
      }
  
      form.$$clearControlValidity(control);
      arrayRemove(controls, control);
    };
  
    form.$$clearControlValidity = function(control) {
      forEach(form.$pending, clear);
      forEach(errors, clear);
  
      function clear(queue, validationToken) {
        form.$setValidity(validationToken, true, control);
      }
  
      parentForm.$$clearControlValidity(form);
    };
  
    form.$$setPending = function(validationToken, control) {
      var pending = form.$pending && form.$pending[validationToken];
  
      if (!pending || !includes(pending, control)) {
        pendingCount++;
        form.$valid = form.$invalid = undefined;
        form.$pending = form.$pending || {};
        if (!pending) {
          pending = form.$pending[validationToken] = [];
        }
        pending.push(control);
        parentForm.$$setPending(validationToken, form);
      }
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$setValidity
     *
     * @description
     * Sets the validity of a form control.
     *
     * This method will also propagate to parent forms.
     */
    form.$setValidity = function(validationToken, isValid, control) {
      var queue = errors[validationToken];
      var pendingChange, pending = form.$pending && form.$pending[validationToken];
  
      if (pending) {
        pendingChange = indexOf(pending, control) >= 0;
        if (pendingChange) {
          arrayRemove(pending, control);
          pendingCount--;
  
          if (pending.length === 0) {
            delete form.$pending[validationToken];
          }
        }
      }
  
      var pendingNoMore = form.$pending && pendingCount === 0;
      if (pendingNoMore) {
        form.$pending = undefined;
      }
  
      if (isValid) {
        if (queue || pendingChange) {
          if (queue) {
            arrayRemove(queue, control);
          }
          if (!queue || !queue.length) {
            if (errors[validationToken]) {
              invalidCount--;
            }
            if (!invalidCount) {
              if (!form.$pending) {
                toggleValidCss(isValid);
                form.$valid = true;
                form.$invalid = false;
              }
            } else if(pendingNoMore) {
              toggleValidCss(false);
              form.$valid = false;
              form.$invalid = true;
            }
            errors[validationToken] = false;
            toggleValidCss(true, validationToken);
            parentForm.$setValidity(validationToken, true, form);
          }
        }
      } else {
        if (!form.$pending) {
          form.$valid = false;
          form.$invalid = true;
        }
  
        if (!invalidCount) {
          toggleValidCss(isValid);
        }
        if (queue) {
          if (includes(queue, control)) return;
        } else {
          errors[validationToken] = queue = [];
          invalidCount++;
          toggleValidCss(false, validationToken);
          parentForm.$setValidity(validationToken, false, form);
        }
        queue.push(control);
      }
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$setDirty
     *
     * @description
     * Sets the form to a dirty state.
     *
     * This method can be called to add the 'ng-dirty' class and set the form to a dirty
     * state (ng-dirty class). This method will also propagate to parent forms.
     */
    form.$setDirty = function() {
      $animate.removeClass(element, PRISTINE_CLASS);
      $animate.addClass(element, DIRTY_CLASS);
      form.$dirty = true;
      form.$pristine = false;
      parentForm.$setDirty();
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$setPristine
     *
     * @description
     * Sets the form to its pristine state.
     *
     * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
     * state (ng-pristine class). This method will also propagate to all the controls contained
     * in this form.
     *
     * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
     * saving or resetting it.
     */
    form.$setPristine = function () {
      $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
      form.$dirty = false;
      form.$pristine = true;
      form.$submitted = false;
      forEach(controls, function(control) {
        control.$setPristine();
      });
    };
  
    /**
     * @ngdoc method
     * @name form.FormController#$setSubmitted
     *
     * @description
     * Sets the form to its submitted state.
     */
    form.$setSubmitted = function () {
      $animate.addClass(element, SUBMITTED_CLASS);
      form.$submitted = true;
      parentForm.$setSubmitted();
    };
  }
  
  /**
   * @ngdoc directive
   * @name ngForm
   * @restrict EAC
   *
   * @description
   * Nestable alias of {@link ng.directive:form `form`} directive. HTML
   * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
   * sub-group of controls needs to be determined.
   *
   * Note: the purpose of `ngForm` is to group controls,
   * but not to be a replacement for the `<form>` tag with all of its capabilities
   * (e.g. posting to the server, ...).
   *
   * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into
   *                       related scope, under this name.
   *
   */
  
   /**
   * @ngdoc directive
   * @name form
   * @restrict E
   *
   * @description
   * Directive that instantiates
   * {@link form.FormController FormController}.
   *
   * If the `name` attribute is specified, the form controller is published onto the current scope under
   * this name.
   *
   * # Alias: {@link ng.directive:ngForm `ngForm`}
   *
   * In Angular forms can be nested. This means that the outer form is valid when all of the child
   * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
   * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
   * `<form>` but can be nested.  This allows you to have nested forms, which is very useful when
   * using Angular validation directives in forms that are dynamically generated using the
   * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name`
   * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an
   * `ngForm` directive and nest these in an outer `form` element.
   *
   *
   * # CSS classes
   *  - `ng-valid` is set if the form is valid.
   *  - `ng-invalid` is set if the form is invalid.
   *  - `ng-pristine` is set if the form is pristine.
   *  - `ng-dirty` is set if the form is dirty.
   *  - `ng-submitted` is set if the form was submitted.
   *
   * Keep in mind that ngAnimate can detect each of these classes when added and removed.
   *
   *
   * # Submitting a form and preventing the default action
   *
   * Since the role of forms in client-side Angular applications is different than in classical
   * roundtrip apps, it is desirable for the browser not to translate the form submission into a full
   * page reload that sends the data to the server. Instead some javascript logic should be triggered
   * to handle the form submission in an application-specific way.
   *
   * For this reason, Angular prevents the default action (form submission to the server) unless the
   * `<form>` element has an `action` attribute specified.
   *
   * You can use one of the following two ways to specify what javascript method should be called when
   * a form is submitted:
   *
   * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element
   * - {@link ng.directive:ngClick ngClick} directive on the first
    *  button or input field of type submit (input[type=submit])
   *
   * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit}
   * or {@link ng.directive:ngClick ngClick} directives.
   * This is because of the following form submission rules in the HTML specification:
   *
   * - If a form has only one input field then hitting enter in this field triggers form submit
   * (`ngSubmit`)
   * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter
   * doesn't trigger submit
   * - if a form has one or more input fields and one or more buttons or input[type=submit] then
   * hitting enter in any of the input fields will trigger the click handler on the *first* button or
   * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
   *
   * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is
   * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
   * to have access to the updated model.
   *
   * @param {string=} name Name of the form. If specified, the form controller will be published into
   *                       related scope, under this name.
   *
   * ## Animation Hooks
   *
   * Animations in ngForm are triggered when any of the associated CSS classes are added and removed.
   * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any
   * other validations that are performed within the form. Animations in ngForm are similar to how
   * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well
   * as JS animations.
   *
   * The following example shows a simple way to utilize CSS transitions to style a form element
   * that has been rendered as invalid after it has been validated:
   *
   * <pre>
   * //be sure to include ngAnimate as a module to hook into more
   * //advanced animations
   * .my-form {
   *   transition:0.5s linear all;
   *   background: white;
   * }
   * .my-form.ng-invalid {
   *   background: red;
   *   color:white;
   * }
   * </pre>
   *
   * @example
      <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample">
        <file name="index.html">
         <script>
           angular.module('formExample', [])
             .controller('FormController', ['$scope', function($scope) {
               $scope.userType = 'guest';
             }]);
         </script>
         <style>
          .my-form {
            -webkit-transition:all linear 0.5s;
            transition:all linear 0.5s;
            background: transparent;
          }
          .my-form.ng-invalid {
            background: red;
          }
         </style>
         <form name="myForm" ng-controller="FormController" class="my-form">
           userType: <input name="input" ng-model="userType" required>
           <span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
           <tt>userType = {{userType}}</tt><br>
           <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
           <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
           <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
           <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
          </form>
        </file>
        <file name="protractor.js" type="protractor">
          it('should initialize to model', function() {
            var userType = element(by.binding('userType'));
            var valid = element(by.binding('myForm.input.$valid'));
  
            expect(userType.getText()).toContain('guest');
            expect(valid.getText()).toContain('true');
          });
  
          it('should be invalid if empty', function() {
            var userType = element(by.binding('userType'));
            var valid = element(by.binding('myForm.input.$valid'));
            var userInput = element(by.model('userType'));
  
            userInput.clear();
            userInput.sendKeys('');
  
            expect(userType.getText()).toEqual('userType =');
            expect(valid.getText()).toContain('false');
          });
        </file>
      </example>
   *
   */
  var formDirectiveFactory = function(isNgForm) {
    return ['$timeout', function($timeout) {
      var formDirective = {
        name: 'form',
        restrict: isNgForm ? 'EAC' : 'E',
        controller: FormController,
        compile: function() {
          return {
            pre: function(scope, formElement, attr, controller) {
              if (!attr.action) {
                // we can't use jq events because if a form is destroyed during submission the default
                // action is not prevented. see #1238
                //
                // IE 9 is not affected because it doesn't fire a submit event and try to do a full
                // page reload if the form was destroyed by submission of the form via a click handler
                // on a button in the form. Looks like an IE9 specific bug.
                var handleFormSubmission = function(event) {
                  scope.$apply(function() {
                    controller.$commitViewValue();
                    controller.$setSubmitted();
                  });
  
                  event.preventDefault
                    ? event.preventDefault()
                    : event.returnValue = false; // IE
                };
  
                addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
  
                // unregister the preventDefault listener so that we don't not leak memory but in a
                // way that will achieve the prevention of the default action.
                formElement.on('$destroy', function() {
                  $timeout(function() {
                    removeEventListenerFn(formElement[0], 'submit', handleFormSubmission);
                  }, 0, false);
                });
              }
  
              var parentFormCtrl = formElement.parent().controller('form'),
                  alias = attr.name || attr.ngForm;
  
              if (alias) {
                setter(scope, alias, controller, alias);
              }
              if (parentFormCtrl) {
                formElement.on('$destroy', function() {
                  parentFormCtrl.$removeControl(controller);
                  if (alias) {
                    setter(scope, alias, undefined, alias);
                  }
                  extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
                });
              }
            }
          };
        }
      };
  
      return formDirective;
    }];
  };
  
  var formDirective = formDirectiveFactory();
  var ngFormDirective = formDirectiveFactory(true);
  