'use strict';


function $TimeoutProvider() {
  this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
       function($rootScope,   $browser,   $q,   $exceptionHandler) {
    var deferreds = {};


     /**
      * @ngdoc function
      * @name ng.$timeout
      * @requires $browser
      *
      * @description
      * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
      * block and delegates any exceptions to
      * {@link ng.$exceptionHandler $exceptionHandler} service.
      *
      * The return value of registering a timeout function is a promise, which will be resolved when
      * the timeout is reached and the timeout function is executed.
      *
      * To cancel a timeout request, call `$timeout.cancel(promise)`.
      *
      * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
      * synchronously flush the queue of deferred functions.
      *
      * @param {function()} fn A function, whose execution should be delayed.
      * @param {number=} [delay=0] Delay in milliseconds.
      * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
      *   will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
      * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
      *   promise will be resolved with is the return value of the `fn` function.
      * 
      * @example
      <doc:example module="time">
        <doc:source>
          <script>
            function Ctrl2($scope,$timeout) {
              $scope.format = 'M/d/yy h:mm:ss a';
              $scope.blood_1 = 100;
              $scope.blood_2 = 120;

              var stop;
              $scope.fight = function() {
                stop = $timeout(function() {
                  if ($scope.blood_1 > 0 && $scope.blood_2 > 0) {
                      $scope.blood_1 = $scope.blood_1 - 3;
                      $scope.blood_2 = $scope.blood_2 - 4;
                      $scope.fight();
                  } else {
                      $timeout.cancel(stop);
                  }
                }, 100);
              };

              $scope.stopFight = function() {
                $timeout.cancel(stop);
              };

              $scope.resetFight = function() {
                $scope.blood_1 = 100;
                $scope.blood_2 = 120;
              }
            }

            angular.module('time', [])
              // Register the 'myCurrentTime' directive factory method.
              // We inject $timeout and dateFilter service since the factory method is DI.
              .directive('myCurrentTime', function($timeout, dateFilter) {
                // return the directive link function. (compile function not needed)
                return function(scope, element, attrs) {
                  var format,  // date format
                  timeoutId; // timeoutId, so that we can cancel the time updates

                  // used to update the UI
                  function updateTime() {
                    element.text(dateFilter(new Date(), format));
                  }

                  // watch the expression, and update the UI on change.
                  scope.$watch(attrs.myCurrentTime, function(value) {
                    format = value;
                    updateTime();
                  });

                  // schedule update in one second
                  function updateLater() {
                    // save the timeoutId for canceling
                    timeoutId = $timeout(function() {
                      updateTime(); // update DOM
                      updateLater(); // schedule another update
                    }, 1000);
                  }

                  // listen on DOM destroy (removal) event, and cancel the next UI update
                  // to prevent updating time ofter the DOM element was removed.
                  element.bind('$destroy', function() {
                    $timeout.cancel(timeoutId);
                  });

                  updateLater(); // kick off the UI update process.
                }
              });
          </script>

          <div>
            <div ng-controller="Ctrl2">
              Date format: <input ng-model="format"> <hr/>
              Current time is: <span my-current-time="format"></span>
              <hr/>
              Blood 1 : <font color='red'>{{blood_1}}</font>
              Blood 2 : <font color='red'>{{blood_2}}</font>
              <button type="button" data-ng-click="fight()">Fight</button>
              <button type="button" data-ng-click="stopFight()">StopFight</button>
              <button type="button" data-ng-click="resetFight()">resetFight</button>
            </div>
          </div>

        </doc:source>
      </doc:example>
      */
    function timeout(fn, delay, invokeApply) {
      var deferred = $q.defer(),
          promise = deferred.promise,
          skipApply = (isDefined(invokeApply) && !invokeApply),
          timeoutId;

      timeoutId = $browser.defer(function() {
        try {
          deferred.resolve(fn());
        } catch(e) {
          deferred.reject(e);
          $exceptionHandler(e);
        }
        finally {
          delete deferreds[promise.$$timeoutId];
        }

        if (!skipApply) $rootScope.$apply();
      }, delay);

      promise.$$timeoutId = timeoutId;
      deferreds[timeoutId] = deferred;

      return promise;
    }


     /**
      * @ngdoc function
      * @name ng.$timeout#cancel
      * @methodOf ng.$timeout
      *
      * @description
      * Cancels a task associated with the `promise`. As a result of this, the promise will be
      * resolved with a rejection.
      *
      * @param {Promise=} promise Promise returned by the `$timeout` function.
      * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
      *   canceled.
      */
    timeout.cancel = function(promise) {
      if (promise && promise.$$timeoutId in deferreds) {
        deferreds[promise.$$timeoutId].reject('canceled');
        delete deferreds[promise.$$timeoutId];
        return $browser.defer.cancel(promise.$$timeoutId);
      }
      return false;
    };

    return timeout;
  }];
}
