/**

* @ngdoc object
* @name ui.router.router.$urlRouterProvider
*
* @requires ui.router.util.$urlMatcherFactoryProvider
* @requires $locationProvider
*
* @description
* `$urlRouterProvider` has the responsibility of watching `$location`. 
* When `$location` changes it runs through a list of rules one by one until a 
* match is found. `$urlRouterProvider` is used behind the scenes anytime you specify 
* a url in a state configuration. All urls are compiled into a UrlMatcher object.
*
* There are several methods on `$urlRouterProvider` that make it useful to use directly
* in your module config.
*/

$UrlRouterProvider.$inject = [‘$locationProvider’, ‘$urlMatcherFactoryProvider’]; function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {

var rules = [], otherwise = null, interceptDeferred = false, listener;

// Returns a string that is a prefix of all strings matching the RegExp
function regExpPrefix(re) {
  var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
  return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
}

// Interpolates matched values into a String.replace()-style pattern
function interpolate(pattern, match) {
  return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
    return match[what === '$' ? 0 : Number(what)];
  });
}

/**
 * @ngdoc function
 * @name ui.router.router.$urlRouterProvider#rule
 * @methodOf ui.router.router.$urlRouterProvider
 *
 * @description
 * Defines rules that are used by `$urlRouterProvider` to find matches for
 * specific URLs.
 *
 * @example
 * <pre>
 * var app = angular.module('app', ['ui.router.router']);
 *
 * app.config(function ($urlRouterProvider) {
 *   // Here's an example of how you might allow case insensitive urls
 *   $urlRouterProvider.rule(function ($injector, $location) {
 *     var path = $location.path(),
 *         normalized = path.toLowerCase();
 *
 *     if (path !== normalized) {
 *       return normalized;
 *     }
 *   });
 * });
 * </pre>
 *
 * @param {object} rule Handler function that takes `$injector` and `$location`
 * services as arguments. You can use them to return a valid path as a string.
 *
 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
 */
this.rule = function (rule) {
  if (!isFunction(rule)) throw new Error("'rule' must be a function");
  rules.push(rule);
  return this;
};

/**
 * @ngdoc object
 * @name ui.router.router.$urlRouterProvider#otherwise
 * @methodOf ui.router.router.$urlRouterProvider
 *
 * @description
 * Defines a path that is used when an invalid route is requested.
 *
 * @example
 * <pre>
 * var app = angular.module('app', ['ui.router.router']);
 *
 * app.config(function ($urlRouterProvider) {
 *   // if the path doesn't match any of the urls you configured
 *   // otherwise will take care of routing the user to the
 *   // specified url
 *   $urlRouterProvider.otherwise('/index');
 *
 *   // Example of using function rule as param
 *   $urlRouterProvider.otherwise(function ($injector, $location) {
 *     return '/a/valid/url';
 *   });
 * });
 * </pre>
 *
 * @param {string|object} rule The url path you want to redirect to or a function 
 * rule that returns the url path. The function version is passed two params: 
 * `$injector` and `$location` services, and must return a url string.
 *
 * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
 */
this.otherwise = function (rule) {
  if (isString(rule)) {
    var redirect = rule;
    rule = function () { return redirect; };
  }
  else if (!isFunction(rule)) throw new Error("'rule' must be a function");
  otherwise = rule;
  return this;
};

function handleIfMatch($injector, handler, match) {
  if (!match) return false;
  var result = $injector.invoke(handler, handler, { $match: match });
  return isDefined(result) ? result : true;
}

/**
 * @ngdoc function
 * @name ui.router.router.$urlRouterProvider#when
 * @methodOf ui.router.router.$urlRouterProvider
 *
 * @description
 * Registers a handler for a given url matching. if handle is a string, it is
 * treated as a redirect, and is interpolated according to the syntax of match
 * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
 *
 * If the handler is a function, it is injectable. It gets invoked if `$location`
 * matches. You have the option of inject the match object as `$match`.
 *
 * The handler can return
 *
 * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
 *   will continue trying to find another one that matches.
 * - **string** which is treated as a redirect and passed to `$location.url()`
 * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
 *
 * @example
 * <pre>
 * var app = angular.module('app', ['ui.router.router']);
 *
 * app.config(function ($urlRouterProvider) {
 *   $urlRouterProvider.when($state.url, function ($match, $stateParams) {
 *     if ($state.$current.navigable !== state ||
 *         !equalForKeys($match, $stateParams) {
 *      $state.transitionTo(state, $match, false);
 *     }
 *   });
 * });
 * </pre>
 *
 * @param {string|object} what The incoming path that you want to redirect.
 * @param {string|object} handler The path you want to redirect your user to.
 */
this.when = function (what, handler) {
  var redirect, handlerIsString = isString(handler);
  if (isString(what)) what = $urlMatcherFactory.compile(what);

  if (!handlerIsString && !isFunction(handler) && !isArray(handler))
    throw new Error("invalid 'handler' in when()");

  var strategies = {
    matcher: function (what, handler) {
      if (handlerIsString) {
        redirect = $urlMatcherFactory.compile(handler);
        handler = ['$match', function ($match) { return redirect.format($match); }];
      }
      return extend(function ($injector, $location) {
        return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
      }, {
        prefix: isString(what.prefix) ? what.prefix : ''
      });
    },
    regex: function (what, handler) {
      if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");

      if (handlerIsString) {
        redirect = handler;
        handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
      }
      return extend(function ($injector, $location) {
        return handleIfMatch($injector, handler, what.exec($location.path()));
      }, {
        prefix: regExpPrefix(what)
      });
    }
  };

  var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };

  for (var n in check) {
    if (check[n]) return this.rule(strategies[n](what, handler));
  }

  throw new Error("invalid 'what' in when()");
};

/**
 * @ngdoc function
 * @name ui.router.router.$urlRouterProvider#deferIntercept
 * @methodOf ui.router.router.$urlRouterProvider
 *
 * @description
 * Disables (or enables) deferring location change interception.
 *
 * If you wish to customize the behavior of syncing the URL (for example, if you wish to
 * defer a transition but maintain the current URL), call this method at configuration time.
 * Then, at run time, call `$urlRouter.listen()` after you have configured your own
 * `$locationChangeSuccess` event handler.
 *
 * @example
 * <pre>
 * var app = angular.module('app', ['ui.router.router']);
 *
 * app.config(function ($urlRouterProvider) {
 *
 *   // Prevent $urlRouter from automatically intercepting URL changes;
 *   // this allows you to configure custom behavior in between
 *   // location changes and route synchronization:
 *   $urlRouterProvider.deferIntercept();
 *
 * }).run(function ($rootScope, $urlRouter, UserService) {
 *
 *   $rootScope.$on('$locationChangeSuccess', function(e) {
 *     // UserService is an example service for managing user state
 *     if (UserService.isLoggedIn()) return;
 *
 *     // Prevent $urlRouter's default handler from firing
 *     e.preventDefault();
 *
 *     UserService.handleLogin().then(function() {
 *       // Once the user has logged in, sync the current URL
 *       // to the router:
 *       $urlRouter.sync();
 *     });
 *   });
 *
 *   // Configures $urlRouter's listener *after* your custom listener
 *   $urlRouter.listen();
 * });
 * </pre>
 *
 * @param {boolean} defer Indicates whether to defer location change interception. Passing
          no parameter is equivalent to `true`.
 */
this.deferIntercept = function (defer) {
  if (defer === undefined) defer = true;
  interceptDeferred = defer;
};

/**
 * @ngdoc object
 * @name ui.router.router.$urlRouter
 *
 * @requires $location
 * @requires $rootScope
 * @requires $injector
 * @requires $browser
 *
 * @description
 *
 */
this.$get = $get;
$get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
function $get(   $location,   $rootScope,   $injector,   $browser) {

  var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;

  function appendBasePath(url, isHtml5, absolute) {
    if (baseHref === '/') return url;
    if (isHtml5) return baseHref.slice(0, -1) + url;
    if (absolute) return baseHref.slice(1) + url;
    return url;
  }

  // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
  function update(evt) {
    if (evt && evt.defaultPrevented) return;
    var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
    lastPushedUrl = undefined;
    if (ignoreUpdate) return true;

    function check(rule) {
      var handled = rule($injector, $location);

      if (!handled) return false;
      if (isString(handled)) $location.replace().url(handled);
      return true;
    }
    var n = rules.length, i;

    for (i = 0; i < n; i++) {
      if (check(rules[i])) return;
    }
    // always check otherwise last to allow dynamic updates to the set of rules
    if (otherwise) check(otherwise);
  }

  function listen() {
    listener = listener || $rootScope.$on('$locationChangeSuccess', update);
    return listener;
  }

  if (!interceptDeferred) listen();

  return {
    /**
     * @ngdoc function
     * @name ui.router.router.$urlRouter#sync
     * @methodOf ui.router.router.$urlRouter
     *
     * @description
     * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
     * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
     * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
     * with the transition by calling `$urlRouter.sync()`.
     *
     * @example
     * <pre>
     * angular.module('app', ['ui.router'])
     *   .run(function($rootScope, $urlRouter) {
     *     $rootScope.$on('$locationChangeSuccess', function(evt) {
     *       // Halt state change from even starting
     *       evt.preventDefault();
     *       // Perform custom logic
     *       var meetsRequirement = ...
     *       // Continue with the update and state transition if logic allows
     *       if (meetsRequirement) $urlRouter.sync();
     *     });
     * });
     * </pre>
     */
    sync: function() {
      update();
    },

    listen: function() {
      return listen();
    },

    update: function(read) {
      if (read) {
        location = $location.url();
        return;
      }
      if ($location.url() === location) return;

      $location.url(location);
      $location.replace();
    },

    push: function(urlMatcher, params, options) {
      $location.url(urlMatcher.format(params || {}));
      lastPushedUrl = options && options.$$avoidResync ? $location.url() : undefined;
      if (options && options.replace) $location.replace();
    },

    /**
     * @ngdoc function
     * @name ui.router.router.$urlRouter#href
     * @methodOf ui.router.router.$urlRouter
     *
     * @description
     * A URL generation method that returns the compiled URL for a given
     * {@link ui.router.util.type:UrlMatcher `UrlMatcher`}, populated with the provided parameters.
     *
     * @example
     * <pre>
     * $bob = $urlRouter.href(new UrlMatcher("/about/:person"), {
     *   person: "bob"
     * });
     * // $bob == "/about/bob";
     * </pre>
     *
     * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
     * @param {object=} params An object of parameter values to fill the matcher's required parameters.
     * @param {object=} options Options object. The options are:
     *
     * - **`absolute`** - {boolean=false},  If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
     *
     * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
     */
    href: function(urlMatcher, params, options) {
      if (!urlMatcher.validates(params)) return null;

      var isHtml5 = $locationProvider.html5Mode();
      if (angular.isObject(isHtml5)) {
        isHtml5 = isHtml5.enabled;
      }

      var url = urlMatcher.format(params);
      options = options || {};

      if (!isHtml5 && url !== null) {
        url = "#" + $locationProvider.hashPrefix() + url;
      }
      url = appendBasePath(url, isHtml5, options.absolute);

      if (!options.absolute || !url) {
        return url;
      }

      var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
      port = (port === 80 || port === 443 ? '' : ':' + port);

      return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
    }
  };
}

}

angular.module(‘ui.router.router’).provider(‘$urlRouter’, $UrlRouterProvider);