/**

* @ngdoc object
* @name ui.router.util.$resolve
*
* @requires $q
* @requires $injector
*
* @description
* Manages resolution of (acyclic) graphs of promises.
*/

$Resolve.$inject = [‘$q’, ‘$injector’]; function $Resolve( $q, $injector) {

var VISIT_IN_PROGRESS = 1,
    VISIT_DONE = 2,
    NOTHING = {},
    NO_DEPENDENCIES = [],
    NO_LOCALS = NOTHING,
    NO_PARENT = extend($q.when(NOTHING), { $$promises: NOTHING, $$values: NOTHING });

/**
 * @ngdoc function
 * @name ui.router.util.$resolve#study
 * @methodOf ui.router.util.$resolve
 *
 * @description
 * Studies a set of invocables that are likely to be used multiple times.
 * <pre>
 * $resolve.study(invocables)(locals, parent, self)
 * </pre>
 * is equivalent to
 * <pre>
 * $resolve.resolve(invocables, locals, parent, self)
 * </pre>
 * but the former is more efficient (in fact `resolve` just calls `study` 
 * internally).
 *
 * @param {object} invocables Invocable objects
 * @return {function} a function to pass in locals, parent and self
 */
this.study = function (invocables) {
  if (!isObject(invocables)) throw new Error("'invocables' must be an object");
  var invocableKeys = objectKeys(invocables || {});

  // Perform a topological sort of invocables to build an ordered plan
  var plan = [], cycle = [], visited = {};
  function visit(value, key) {
    if (visited[key] === VISIT_DONE) return;

    cycle.push(key);
    if (visited[key] === VISIT_IN_PROGRESS) {
      cycle.splice(0, indexOf(cycle, key));
      throw new Error("Cyclic dependency: " + cycle.join(" -> "));
    }
    visited[key] = VISIT_IN_PROGRESS;

    if (isString(value)) {
      plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
    } else {
      var params = $injector.annotate(value);
      forEach(params, function (param) {
        if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
      });
      plan.push(key, value, params);
    }

    cycle.pop();
    visited[key] = VISIT_DONE;
  }
  forEach(invocables, visit);
  invocables = cycle = visited = null; // plan is all that's required

  function isResolve(value) {
    return isObject(value) && value.then && value.$$promises;
  }

  return function (locals, parent, self) {
    if (isResolve(locals) && self === undefined) {
      self = parent; parent = locals; locals = null;
    }
    if (!locals) locals = NO_LOCALS;
    else if (!isObject(locals)) {
      throw new Error("'locals' must be an object");
    }       
    if (!parent) parent = NO_PARENT;
    else if (!isResolve(parent)) {
      throw new Error("'parent' must be a promise returned by $resolve.resolve()");
    }

    // To complete the overall resolution, we have to wait for the parent
    // promise and for the promise for each invokable in our plan.
    var resolution = $q.defer(),
        result = resolution.promise,
        promises = result.$$promises = {},
        values = extend({}, locals),
        wait = 1 + plan.length/3,
        merged = false;

    function done() {
      // Merge parent values we haven't got yet and publish our own $$values
      if (!--wait) {
        if (!merged) merge(values, parent.$$values); 
        result.$$values = values;
        result.$$promises = result.$$promises || true; // keep for isResolve()
        delete result.$$inheritedValues;
        resolution.resolve(values);
      }
    }

    function fail(reason) {
      result.$$failure = reason;
      resolution.reject(reason);
    }

    // Short-circuit if parent has already failed
    if (isDefined(parent.$$failure)) {
      fail(parent.$$failure);
      return result;
    }

    if (parent.$$inheritedValues) {
      merge(values, omit(parent.$$inheritedValues, invocableKeys));
    }

    // Merge parent values if the parent has already resolved, or merge
    // parent promises and wait if the parent resolve is still in progress.
    extend(promises, parent.$$promises);
    if (parent.$$values) {
      merged = merge(values, omit(parent.$$values, invocableKeys));
      result.$$inheritedValues = omit(parent.$$values, invocableKeys);
      done();
    } else {
      if (parent.$$inheritedValues) {
        result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
      }        
      parent.then(done, fail);
    }

    // Process each invocable in the plan, but ignore any where a local of the same name exists.
    for (var i=0, ii=plan.length; i<ii; i+=3) {
      if (locals.hasOwnProperty(plan[i])) done();
      else invoke(plan[i], plan[i+1], plan[i+2]);
    }

    function invoke(key, invocable, params) {
      // Create a deferred for this invocation. Failures will propagate to the resolution as well.
      var invocation = $q.defer(), waitParams = 0;
      function onfailure(reason) {
        invocation.reject(reason);
        fail(reason);
      }
      // Wait for any parameter that we have a promise for (either from parent or from this
      // resolve; in that case study() will have made sure it's ordered before us in the plan).
      forEach(params, function (dep) {
        if (promises.hasOwnProperty(dep) && !locals.hasOwnProperty(dep)) {
          waitParams++;
          promises[dep].then(function (result) {
            values[dep] = result;
            if (!(--waitParams)) proceed();
          }, onfailure);
        }
      });
      if (!waitParams) proceed();
      function proceed() {
        if (isDefined(result.$$failure)) return;
        try {
          invocation.resolve($injector.invoke(invocable, self, values));
          invocation.promise.then(function (result) {
            values[key] = result;
            done();
          }, onfailure);
        } catch (e) {
          onfailure(e);
        }
      }
      // Publish promise synchronously; invocations further down in the plan may depend on it.
      promises[key] = invocation.promise;
    }

    return result;
  };
};

/**
 * @ngdoc function
 * @name ui.router.util.$resolve#resolve
 * @methodOf ui.router.util.$resolve
 *
 * @description
 * Resolves a set of invocables. An invocable is a function to be invoked via 
 * `$injector.invoke()`, and can have an arbitrary number of dependencies. 
 * An invocable can either return a value directly,
 * or a `$q` promise. If a promise is returned it will be resolved and the 
 * resulting value will be used instead. Dependencies of invocables are resolved 
 * (in this order of precedence)
 *
 * - from the specified `locals`
 * - from another invocable that is part of this `$resolve` call
 * - from an invocable that is inherited from a `parent` call to `$resolve` 
 *   (or recursively
 * - from any ancestor `$resolve` of that parent).
 *
 * The return value of `$resolve` is a promise for an object that contains 
 * (in this order of precedence)
 *
 * - any `locals` (if specified)
 * - the resolved return values of all injectables
 * - any values inherited from a `parent` call to `$resolve` (if specified)
 *
 * The promise will resolve after the `parent` promise (if any) and all promises 
 * returned by injectables have been resolved. If any invocable 
 * (or `$injector.invoke`) throws an exception, or if a promise returned by an 
 * invocable is rejected, the `$resolve` promise is immediately rejected with the 
 * same error. A rejection of a `parent` promise (if specified) will likewise be 
 * propagated immediately. Once the `$resolve` promise has been rejected, no 
 * further invocables will be called.
 * 
 * Cyclic dependencies between invocables are not permitted and will caues `$resolve`
 * to throw an error. As a special case, an injectable can depend on a parameter 
 * with the same name as the injectable, which will be fulfilled from the `parent` 
 * injectable of the same name. This allows inherited values to be decorated. 
 * Note that in this case any other injectable in the same `$resolve` with the same
 * dependency would see the decorated value, not the inherited value.
 *
 * Note that missing dependencies -- unlike cyclic dependencies -- will cause an 
 * (asynchronous) rejection of the `$resolve` promise rather than a (synchronous) 
 * exception.
 *
 * Invocables are invoked eagerly as soon as all dependencies are available. 
 * This is true even for dependencies inherited from a `parent` call to `$resolve`.
 *
 * As a special case, an invocable can be a string, in which case it is taken to 
 * be a service name to be passed to `$injector.get()`. This is supported primarily 
 * for backwards-compatibility with the `resolve` property of `$routeProvider` 
 * routes.
 *
 * @param {object} invocables functions to invoke or 
 * `$injector` services to fetch.
 * @param {object} locals  values to make available to the injectables
 * @param {object} parent  a promise returned by another call to `$resolve`.
 * @param {object} self  the `this` for the invoked methods
 * @return {object} Promise for an object that contains the resolved return value
 * of all invocables, as well as any inherited and local values.
 */
this.resolve = function (invocables, locals, parent, self) {
  return this.study(invocables)(locals, parent, self);
};

}

angular.module(‘ui.router.util’).service(‘$resolve’, $Resolve);