/**
 * UI-Router Extras: Sticky states, Future States, Deep State Redirect, Transition promise
 * Monolithic build (all modules)
 * @version 0.1.0
 * @link http://christopherthielen.github.io/ui-router-extras/
 * @license MIT License, http://www.opensource.org/licenses/MIT
 */
(function (root, factory) {
    'use strict';

    if (typeof define === 'function' && define.amd) {
        define(['angular'], function (angular) {
            factory(angular);
        });
    } else if (typeof exports === 'object') {
        factory(require('angular'));
    } else {
        factory(root.angular);
    }
}(this, function (angular, undefined) {
    var mod_core = angular.module("ct.ui.router.extras.core", [ "ui.router" ]);

var internalStates = {}, stateRegisteredCallbacks = [];
mod_core.config([ '$stateProvider', '$injector', function ($stateProvider, $injector) {
  // Decorate any state attribute in order to get access to the internal state representation.
  $stateProvider.decorator('parent', function (state, parentFn) {
    // Capture each internal UI-Router state representations as opposed to the user-defined state object.
    // The internal state is, e.g., the state returned by $state.$current as opposed to $state.current
    internalStates[state.self.name] = state;
    // Add an accessor for the internal state from the user defined state
    state.self.$$state = function () {
      return internalStates[state.self.name];
    };

    angular.forEach(stateRegisteredCallbacks, function(callback) { callback(state); });
    return parentFn(state);
  });
}]);

var DEBUG = false;

var forEach = angular.forEach;
var extend = angular.extend;
var isArray = angular.isArray;

var map = function (collection, callback) {
  "use strict";
  var result = [];
  forEach(collection, function (item, index) {
    result.push(callback(item, index));
  });
  return result;
};

var keys = function (collection) {
  "use strict";
  return map(collection, function (collection, key) {
    return key;
  });
};

var filter = function (collection, callback) {
  "use strict";
  var result = [];
  forEach(collection, function (item, index) {
    if (callback(item, index)) {
      result.push(item);
    }
  });
  return result;
};

var filterObj = function (collection, callback) {
  "use strict";
  var result = {};
  forEach(collection, function (item, index) {
    if (callback(item, index)) {
      result[index] = item;
    }
  });
  return result;
};

// Duplicates code in UI-Router common.js
function ancestors(first, second) {
  var path = [];

  for (var n in first.path) {
    if (first.path[n] !== second.path[n]) break;
    path.push(first.path[n]);
  }
  return path;
}

// Duplicates code in UI-Router common.js
function objectKeys(object) {
  if (Object.keys) {
    return Object.keys(object);
  }
  var result = [];

  angular.forEach(object, function (val, key) {
    result.push(key);
  });
  return result;
}

/**
 * like objectKeys, but includes keys from prototype chain.
 * @param object the object whose prototypal keys will be returned
 * @param ignoreKeys an array of keys to ignore
 */
// Duplicates code in UI-Router common.js
function protoKeys(object, ignoreKeys) {
  var result = [];
  for (var key in object) {
    if (!ignoreKeys || ignoreKeys.indexOf(key) === -1)
      result.push(key);
  }
  return result;
}

// Duplicates code in UI-Router common.js
function arraySearch(array, value) {
  if (Array.prototype.indexOf) {
    return array.indexOf(value, Number(arguments[2]) || 0);
  }
  var len = array.length >>> 0, from = Number(arguments[2]) || 0;
  from = (from < 0) ? Math.ceil(from) : Math.floor(from);

  if (from < 0) from += len;

  for (; from < len; from++) {
    if (from in array && array[from] === value) return from;
  }
  return -1;
}

// Duplicates code in UI-Router common.js
// Added compatibility code  (isArray check) to support both 0.2.x and 0.3.x series of UI-Router.
function inheritParams(currentParams, newParams, $current, $to) {
  var parents = ancestors($current, $to), parentParams, inherited = {}, inheritList = [];

  for (var i in parents) {
    if (!parents[i].params) continue;
    // This test allows compatibility with 0.2.x and 0.3.x (optional and object params)
    parentParams = isArray(parents[i].params) ? parents[i].params : objectKeys(parents[i].params);
    if (!parentParams.length) continue;

    for (var j in parentParams) {
      if (arraySearch(inheritList, parentParams[j]) >= 0) continue;
      inheritList.push(parentParams[j]);
      inherited[parentParams[j]] = currentParams[parentParams[j]];
    }
  }
  return extend({}, inherited, newParams);
}

function inherit(parent, extra) {
  return extend(new (extend(function () { }, {prototype: parent}))(), extra);
}

function onStateRegistered(callback) { stateRegisteredCallbacks.push(callback); }

mod_core.provider("uirextras_core", function() {
  var core = {
    internalStates: internalStates,
    onStateRegistered: onStateRegistered,
    forEach: forEach,
    extend: extend,
    isArray: isArray,
    map: map,
    keys: keys,
    filter: filter,
    filterObj: filterObj,
    ancestors: ancestors,
    objectKeys: objectKeys,
    protoKeys: protoKeys,
    arraySearch: arraySearch,
    inheritParams: inheritParams,
    inherit: inherit
  };

  angular.extend(this, core);

  this.$get = function() {
    return core;
  };
});


var ignoreDsr;
function resetIgnoreDsr() {
  ignoreDsr = undefined;
}

// Decorate $state.transitionTo to gain access to the last transition.options variable.
// This is used to process the options.ignoreDsr option
angular.module('ct.ui.router.extras.dsr', [ 'ct.ui.router.extras.core' ]).config([ "$provide", function ($provide) {
  var $state_transitionTo;
  $provide.decorator("$state", ['$delegate', '$q', function ($state, $q) {
    $state_transitionTo = $state.transitionTo;
    $state.transitionTo = function (to, toParams, options) {
      if (options.ignoreDsr) {
        ignoreDsr = options.ignoreDsr;
      }

      return $state_transitionTo.apply($state, arguments).then(
        function (result) {
          resetIgnoreDsr();
          return result;
        },
        function (err) {
          resetIgnoreDsr();
          return $q.reject(err);
        }
      );
    };
    return $state;
  }]);
}]);

angular.module('ct.ui.router.extras.dsr').service("$deepStateRedirect", [ '$rootScope', '$state', '$injector', function ($rootScope, $state, $injector) {
  var lastSubstate = {};
  var deepStateRedirectsByName = {};

  var REDIRECT = "Redirect", ANCESTOR_REDIRECT = "AncestorRedirect";

  function computeDeepStateStatus(state) {
    var name = state.name;
    if (deepStateRedirectsByName.hasOwnProperty(name))
      return deepStateRedirectsByName[name];
    recordDeepStateRedirectStatus(name);
  }

  function getConfig(state) {
    var declaration = state.deepStateRedirect || state.dsr;
    if (!declaration) return { dsr: false };
    var dsrCfg = { dsr: true };

    if (angular.isFunction(declaration)) {
      dsrCfg.fn = declaration;
    } else if (angular.isObject(declaration)) {
      dsrCfg = angular.extend(dsrCfg, declaration);
    }

    if (angular.isString(dsrCfg['default'])) {
      dsrCfg['default'] = { state: dsrCfg['default'] };
    }

    if (!dsrCfg.fn) {
      dsrCfg.fn = [ '$dsr$', function($dsr$) {
        return $dsr$.redirect.state != $dsr$.to.state;
      } ];
    }
    return dsrCfg;
  }

  function recordDeepStateRedirectStatus(stateName) {
    var state = $state.get(stateName);
    if (!state) return false;
    var cfg = getConfig(state);
    if (cfg.dsr) {
      deepStateRedirectsByName[state.name] = REDIRECT;
      if (lastSubstate[stateName] === undefined)
        lastSubstate[stateName] = {};
    }

    var parent = state.$$state && state.$$state().parent;
    if (parent) {
      var parentStatus = recordDeepStateRedirectStatus(parent.self.name);
      if (parentStatus && deepStateRedirectsByName[state.name] === undefined) {
        deepStateRedirectsByName[state.name] = ANCESTOR_REDIRECT;
      }
    }
    return deepStateRedirectsByName[state.name] || false;
  }

  function getMatchParams(params, dsrParams) {
    if (dsrParams === true) dsrParams = Object.keys(params);
    if (dsrParams === null || dsrParams === undefined) dsrParams = [];

    var matchParams = {};
    angular.forEach(dsrParams.sort(), function(name) { matchParams[name] = params[name]; });
    return matchParams;
  }

  function getParamsString(params, dsrParams) {
    var matchParams = getMatchParams(params, dsrParams);
    function safeString(input) { return !input ? input : input.toString(); }
    var paramsToString = {};
    angular.forEach(matchParams, function(val, name) { paramsToString[name] = safeString(val); });
    return angular.toJson(paramsToString);
  }

  $rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState, fromParams) {
    var cfg = getConfig(toState);
    if (ignoreDsr || (computeDeepStateStatus(toState) !== REDIRECT) && !cfg['default']) return;
    // We're changing directly to one of the redirect (tab) states.
    // Get the DSR key for this state by calculating the DSRParams option
    var key = getParamsString(toParams, cfg.params);
    var redirect = lastSubstate[toState.name][key] || cfg['default'];
    if (!redirect) return;

    // we have a last substate recorded
    var $dsr$ = { redirect: { state: redirect.state, params: redirect.params}, to: { state: toState.name, params: toParams } };
    var result = $injector.invoke(cfg.fn, toState, { $dsr$: $dsr$ });
    if (!result) return;
    if (result.state) redirect = result;
    event.preventDefault();
    var redirectParams = getMatchParams(toParams, cfg.params);
    $state.go(redirect.state, angular.extend(redirectParams, redirect.params));
  });

  $rootScope.$on("$stateChangeSuccess", function (event, toState, toParams, fromState, fromParams) {
    var deepStateStatus = computeDeepStateStatus(toState);
    if (deepStateStatus) {
      var name = toState.name;
      angular.forEach(lastSubstate, function (redirect, dsrState) {
        // update Last-SubState&params for each DSR that this transition matches.
        var cfg = getConfig($state.get(dsrState));
        var key = getParamsString(toParams, cfg.params);
        if (toState.$$state().includes[dsrState]) {
          lastSubstate[dsrState][key] = { state: name, params: angular.copy(toParams) };
        }
      });
    }
  });

  return {
    getRedirect: function(dsrState, params) {
      var state = $state.get(dsrState);
      computeDeepStateStatus(state)
      var cfg = getConfig(state);
      var key = getParamsString(params, cfg.params);
      var redirect = lastSubstate[state.name][key] || cfg['default'];
      return redirect;
    },
    reset: function(stateOrName, params) {
      if (!stateOrName) {
        angular.forEach(lastSubstate, function(redirect, dsrState) { lastSubstate[dsrState] = {}; });
      } else {
        var state = $state.get(stateOrName);
        if (!state) throw new Error("Unknown state: " + stateOrName);
        if (lastSubstate[state.name]) {
          if (params) {
            var key = getParamsString(params, getConfig(state).params);
            delete lastSubstate[state.name][key];
          } else {
            lastSubstate[state.name] = {};
          }
        }
      }
    }
  };
}]);

angular.module('ct.ui.router.extras.dsr').run(['$deepStateRedirect', function ($deepStateRedirect) {
  // Make sure $deepStateRedirect is instantiated
}]);

angular.module("ct.ui.router.extras.sticky", [ 'ct.ui.router.extras.core' ]);

var mod_sticky = angular.module("ct.ui.router.extras.sticky");

$StickyStateProvider.$inject = [ '$stateProvider', 'uirextras_coreProvider' ];
function $StickyStateProvider($stateProvider, uirextras_coreProvider) {
  var core = uirextras_coreProvider;
  var inheritParams = core.inheritParams;
  var objectKeys = core.objectKeys;
  var protoKeys = core.protoKeys;
  var forEach = core.forEach;
  var map = core.map;

  // Holds all the states which are inactivated.  Inactivated states can be either sticky states, or descendants of sticky states.
  var inactiveStates = {}; // state.name -> (state)
  var stickyStates = {}; // state.name -> true
  var $state;
  var DEBUG = false;

  // Called by $stateProvider.registerState();
  // registers a sticky state with $stickyStateProvider
  this.registerStickyState = function (state) {
    stickyStates[state.name] = state;
    // console.log("Registered sticky state: ", state);
  };

  this.enableDebug = this.debugMode = function (enabled) {
    if (angular.isDefined(enabled))
      DEBUG = enabled;
    return DEBUG;
  };

  this.$get = [  '$rootScope', '$state', '$stateParams', '$injector', '$log',
    function ($rootScope, $state, $stateParams, $injector, $log) {
      // Each inactive states is either a sticky state, or a child of a sticky state.
      // This function finds the closest ancestor sticky state, then find that state's parent.
      // Map all inactive states to their closest parent-to-sticky state.
      function mapInactives() {
        var mappedStates = {};
        angular.forEach(inactiveStates, function (state, name) {
          var stickyAncestors = getStickyStateStack(state);
          for (var i = 0; i < stickyAncestors.length; i++) {
            var parent = stickyAncestors[i].parent;
            mappedStates[parent.name] = mappedStates[parent.name] || [];
            mappedStates[parent.name].push(state);
          }
          if (mappedStates['']) {
            // This is necessary to compute Transition.inactives when there are sticky states are children to root state.
            mappedStates['__inactives'] = mappedStates[''];  // jshint ignore:line
          }
        });
        return mappedStates;
      }

      function mapInactivesByImmediateParent() {
        var inactivesByAllParents ={};
        forEach(inactiveStates, function(state) {
          forEach(state.path, function(ancestor) {
            if (ancestor === state) return;
            inactivesByAllParents[ancestor.name] = inactivesByAllParents[ancestor.name] || [];
            inactivesByAllParents[ancestor.name].push(state);
          });
        });
        return inactivesByAllParents;
      }

      // Given a state, returns all ancestor states which are sticky.
      // Walks up the view's state's ancestry tree and locates each ancestor state which is marked as sticky.
      // Returns an array populated with only those ancestor sticky states.
      function getStickyStateStack(state) {
        var stack = [];
        if (!state) return stack;
        do {
          if (state.sticky) stack.push(state);
          state = state.parent;
        } while (state);
        stack.reverse();
        return stack;
      }

      // Returns a sticky transition type necessary to enter the state.
      // Transition can be: reactivate, reload, or enter

      // Note: if a state is being reactivated but params dont match, we treat
      // it as a Exit/Enter, thus the special "reload" transition.
      // If a parent inactivated state has "reload" transition type, then
      // all descendant states must also be exit/entered, thus the first line of this function.
      function getEnterTransition(state, stateParams, reloadStateTree, ancestorReloaded) {
        if (ancestorReloaded) return "reload";
        var inactiveState = inactiveStates[state.self.name];
        if (!inactiveState) return "enter";
        if (state.self === reloadStateTree) return "reload";
        var paramsMatch = paramsEqualForState(state.ownParams, stateParams, inactiveState.locals.globals.$stateParams);
        return paramsMatch ? "reactivate" : "reload";
      }

      // Given a state and (optional) stateParams, returns the inactivated state from the inactive sticky state registry.
      function getInactivatedState(state, stateParams) {
        var inactiveState = inactiveStates[state.name];
        if (!inactiveState) return null;
        if (!stateParams) return inactiveState;
        var paramsMatch = paramsEqualForState(state.ownParams, stateParams, inactiveState.locals.globals.$stateParams);
        return paramsMatch ? inactiveState : null;
      }

      function paramsEqualForState(ownParams, stateParams, stateParams2) {
        if (typeof ownParams.$$equals === 'function')
          return ownParams.$$equals(stateParams, stateParams2);
        return equalForKeys(stateParams, stateParams2, ownParams);
      }

      // Duplicates logic in $state.transitionTo, primarily to find the pivot state (i.e., the "keep" value)
      function equalForKeys(a, b, keys) {
        if (!angular.isArray(keys) && angular.isObject(keys)) {
          keys = protoKeys(keys, ["$$keys", "$$values", "$$equals", "$$validates", "$$new", "$$parent"]);
        }
        if (!keys) {
          keys = [];
          for (var n in a) keys.push(n); // Used instead of Object.keys() for IE8 compatibility
        }

        for (var i = 0; i < keys.length; i++) {
          var k = keys[i];
          if (a[k] != b[k]) return false; // Not '===', values aren't necessarily normalized
        }
        return true;
      }

      function calcTreeChanges(transition) {
        var fromPath = transition.fromState.path;
        var toPath = transition.toState.path;
        var toParams = transition.toParams;
        var keep = 0, state = toPath[keep];

        if (transition.options.inherit) {
          toParams = inheritParams($stateParams, toParams || {}, $state.$current, transition.toState);
        }

        while (state && state === fromPath[keep] && paramsEqualForState(state.ownParams, toParams, transition.fromParams)) {
          // We're "keeping" this state. bump keep var and get the next state in toPath for the next iteration.
          state = toPath[++keep];
        }

        return {
          keep: keep,
          retained: fromPath.slice(0, keep),
          exiting: fromPath.slice(keep),
          entering: toPath.slice(keep)
        };
      }


      var stickySupport = {
        getInactiveStates: function () {
          return map(inactiveStates, angular.identity);
        },
        getInactiveStatesByParent: function () {
          return mapInactives();
        },
        // Main API for $stickyState, used by $state.
        // Processes a potential transition, returns an object with the following attributes:
        // {
        //    keep: The number of states being "kept"
        //    inactives: Array of all states which will be inactive if the transition is completed.
        //    reactivatingStates: Array of all states which will be reactivated if the transition is completed.
        //    orphans: Array of previously inactive states, which are being orphaned by the transition
        //        Note: Transitioning directly to an inactive state with inactive children will reactivate the state, but exit all the inactive children.
        //    enter: Enter transition type for all added states.  This is a parallel array to "toStates" array in $state.transitionTo.
        //    exit: Exit transition type for all removed states.  This is a parallel array to "fromStates" array in $state.transitionTo.
        // }
        processTransition: function (transition) {
          var treeChanges = calcTreeChanges(transition);
          var currentInactives = map(inactiveStates, angular.identity);
          var futureInactives, exitingTypes, enteringTypes;
          var keep = treeChanges.keep;


          /////////////////////////////////////////
          // helper functions
          function notIn(array) { return function (elem) { return array.indexOf(elem) === -1; }; }
          function flattenReduce(memo, list) { return memo.concat(list); }
          function uniqReduce(memo, orphan) { if (notIn(memo)(orphan)) memo.push(orphan); return memo; }
          function prop(attr) { return function(obj) { return obj[attr]; } }
          function typeIs(type) { return function(obj) { return obj.type === type; } }
          function isChildOf(state) { return function(other) { return other.parent === state; }; }
          var notEntering = notIn(treeChanges.entering);
          function notSticky(state) { return !state.sticky; }
          ////////////////////////////////////


          // Calculate the "exit" transition types for states being exited in fromPath
          // Exit types will be either "inactivate" or "exit"
          // Two things must be satisfied in order to inactivate the "exiting" states (instead of exit them):
          // - The first element of the exiting path must be sticky
          // - We must be entering any sibling state of the sticky (we can check this using entering.length)
          var shouldInactivate = treeChanges.exiting[0] && treeChanges.exiting[0].sticky && treeChanges.entering.length > 0;
          exitingTypes = treeChanges.exiting.map(function (state) {
              var type = shouldInactivate ? "inactivate" : "exit";
              return { type: type, state: state };
          });


          // Calculate the "enter" transition types for states being entered in toPath
          // Enter types will be either "enter", "reactivate", or "reload" where:
          //   enter: full resolve, no special logic
          //   reactivate: use previous locals
          //   reload: like 'enter', except exit the inactive state before entering it.
          var reloaded = !!transition.options.reload;
          enteringTypes = treeChanges.entering.map(function(state) {
            var type = getEnterTransition(state, transition.toParams, transition.reloadStateTree, reloaded);
            reloaded = reloaded || type === 'reload';
            return { type: type, state: state };
          });

          // Find all the "orphaned" states.  those states that are :
          //  - are siblings of the entering states
          //  - previously inactive
          //  - are not being reactivated (entered)
          //  - are not sticky
          // unioned with:
          //  - children of the toState
          //  - previously inactive
          //
          // Given:
          //   - states A (sticky: true), B, A.foo, A.bar
          //   - A.foo is currently inactive
          //   - B is currently active
          // Orphan case 1)
          //   - Transition to A.bar orphans the inactive state A.foo; it should be exited
          // Orphan case 2)
          //   - Transition directly to A orphans the inactive state A.foo; it should be exited
          //
          // Given:
          //   - states A (sticky: true), B, A.foo (sticky), A.bar
          //   - A.foo is currently inactive
          //   - B is currently active
          // Orphan case 3)
          //   - Transition directly to A orphans the inactive sticky state A.foo; it should be exited
          // Note: transition from B to A.bar does not orphan A.foo
          // Note 2: each orphaned state might be the parent of a larger inactive subtree.
          var orphanedRoots = treeChanges.entering
              // For each entering state in the path, find all sibling states which are currently inactive
              .map(function (entering) { return currentInactives.filter(isChildOf(entering.parent)); })
              // Flatten nested arrays. Now we have an array of inactive states that are children of the ones being entered.
              .reduce(flattenReduce, [])
              // Consider "orphaned": only those children that are themselves not currently being entered
              .filter(notEntering)
              // Consider "orphaned": only those children that are not themselves sticky states.
              .filter(notSticky)
              // Finally, union that set with any inactive children of the "to state"
              .concat(currentInactives.filter(isChildOf(transition.toState)));

          var currentInactivesByParent = mapInactivesByImmediateParent();
          var allOrphans = orphanedRoots
              .map(function(root) { return currentInactivesByParent[root.name] })
              .filter(angular.isDefined)
              .reduce(flattenReduce, [])
              .concat(orphanedRoots)
              // Sort by depth to exit orphans in proper order
              .sort(function (a,b) { return a.name.split(".").length - b.name.split(".").length; });

          // Add them to the list of states being exited.
          var exitOrOrphaned = exitingTypes
              .filter(typeIs("exit"))
              .map(prop("state"))
              .concat(allOrphans);

          // Now calculate the states that will be inactive if this transition succeeds.
          // We have already pushed the transitionType == "inactivate" states to 'inactives'.
          // Second, add all the existing inactive states
          futureInactives = currentInactives
              .filter(notIn(exitOrOrphaned))
              .filter(notIn(treeChanges.entering))
              .concat(exitingTypes.filter(typeIs("inactivate")).map(prop("state")));

          return {
            keep: keep,
            enter: new Array(keep).concat(enteringTypes.map(prop("type"))),
            exit: new Array(keep).concat(exitingTypes.map(prop("type"))),
            inactives: futureInactives,
            reactivatingStates: enteringTypes.filter(typeIs("reactivate")).map(prop("state")),
            orphans: allOrphans
          };
        },

        // Adds a state to the inactivated sticky state registry.
        stateInactivated: function (state) {
          // Keep locals around.
          inactiveStates[state.self.name] = state;
          // Notify states they are being Inactivated (i.e., a different
          // sticky state tree is now active).
          state.self.status = 'inactive';
          if (state.self.onInactivate)
            $injector.invoke(state.self.onInactivate, state.self, state.locals.globals);
        },

        // Removes a previously inactivated state from the inactive sticky state registry
        stateReactivated: function (state) {
          if (inactiveStates[state.self.name]) {
            delete inactiveStates[state.self.name];
          }
          state.self.status = 'entered';
//        if (state.locals == null || state.locals.globals == null) debugger;
          if (state.self.onReactivate)
            $injector.invoke(state.self.onReactivate, state.self, state.locals.globals);
        },

        // Exits all inactivated descendant substates when the ancestor state is exited.
        // When transitionTo is exiting a state, this function is called with the state being exited.  It checks the
        // registry of inactivated states for descendants of the exited state and also exits those descendants.  It then
        // removes the locals and de-registers the state from the inactivated registry.
        stateExiting: function (exiting, exitQueue, onExit) {
          var exitingNames = {};
          angular.forEach(exitQueue, function (state) {
            exitingNames[state.self.name] = true;
          });

          angular.forEach(inactiveStates, function (inactiveExiting, name) {
            // TODO: Might need to run the inactivations in the proper depth-first order?
            if (!exitingNames[name] && inactiveExiting.includes[exiting.name]) {
              if (DEBUG) $log.debug("Exiting " + name + " because it's a substate of " + exiting.name + " and wasn't found in ", exitingNames);
              if (inactiveExiting.self.onExit)
                $injector.invoke(inactiveExiting.self.onExit, inactiveExiting.self, inactiveExiting.locals.globals);
              angular.forEach(inactiveExiting.locals, function(localval, key) {
                delete inactivePseudoState.locals[key];
              });
              inactiveExiting.locals = null;
              inactiveExiting.self.status = 'exited';
              delete inactiveStates[name];
            }
          });

          if (onExit)
            $injector.invoke(onExit, exiting.self, exiting.locals.globals);
          exiting.locals = null;
          exiting.self.status = 'exited';
          delete inactiveStates[exiting.self.name];
        },

        // Removes a previously inactivated state from the inactive sticky state registry
        stateEntering: function (entering, params, onEnter, updateParams) {
          var inactivatedState = getInactivatedState(entering);
          if (inactivatedState && (updateParams || !getInactivatedState(entering, params))) {
            var savedLocals = entering.locals;
            this.stateExiting(inactivatedState);
            entering.locals = savedLocals;
          }
          entering.self.status = 'entered';

          if (onEnter)
            $injector.invoke(onEnter, entering.self, entering.locals.globals);
        },
        reset: function reset(inactiveState, params) {
          function resetOne(state) { stickySupport.reset(state); }
          if (inactiveState === "*") {
            angular.forEach(stickySupport.getInactiveStates(), resetOne);
            return true;
          }
          var state = $state.get(inactiveState);
          if (!state) return false;
          var exiting = getInactivatedState(state, params);
          if (!exiting) return false;
          stickySupport.stateExiting(exiting);
          $rootScope.$broadcast("$viewContentLoading");
          return true;
        }
      };

      return stickySupport;
    }];
}

mod_sticky.provider("$stickyState", $StickyStateProvider);

/**
 * Sticky States makes entire state trees "sticky". Sticky state trees are retained until their parent state is
 * exited. This can be useful to allow multiple modules, peers to each other, each module having its own independent
 * state tree.  The peer modules can be activated and inactivated without any loss of their internal context, including
 * DOM content such as unvalidated/partially filled in forms, and even scroll position.
 *
 * DOM content is retained by declaring a named ui-view in the parent state, and filling it in with a named view from the
 * sticky state.
 *
 * Technical overview:
 *
 * ---PATHS---
 * UI-Router uses state paths to manage entering and exiting of individual states.  Each state "A.B.C.X" has its own path, starting
 * from the root state ("") and ending at the state "X".  The path is composed the final state "X"'s ancestors, e.g.,
 * [ "", "A", "B", "C", "X" ].
 *
 * When a transition is processed, the previous path (fromState.path) is compared with the requested destination path
 * (toState.path).  All states that the from and to paths have in common are "kept" during the transition.  The last
 * "kept" element in the path is the "pivot".
 *
 * ---VIEWS---
 * A View in UI-Router consists of a controller and a template.  Each view belongs to one state, and a state can have many
 * views.  Each view plugs into a ui-view element in the DOM of one of the parent state's view(s).
 *
 * View context is managed in UI-Router using a 'state locals' concept. When a state's views are fully loaded, those views
 * are placed on the states 'locals' object.  Each locals object prototypally inherits from its parent state's locals object.
 * This means that state "A.B.C.X"'s locals object also has all of state "A.B.C"'s locals as well as those from "A.B" and "A".
 * The root state ("") defines no views, but it is included in the protypal inheritance chain.
 *
 * The locals object is used by the ui-view directive to load the template, render the content, create the child scope,
 * initialize the controller, etc.  The ui-view directives caches the locals in a closure variable.  If the locals are
 * identical (===), then the ui-view directive exits early, and does no rendering.
 *
 * In stock UI-Router, when a state is exited, that state's locals object is deleted and those views are cleaned up by
 * the ui-view directive shortly.
 *
 * ---Sticky States---
 * UI-Router Extras keeps views for inactive states live, even when UI-Router thinks it has exited them.  It does this
 * by creating a pseudo state called "__inactives" that is the parent of the root state.  It also then defines a locals
 * object on the "__inactives" state, which the root state protoypally inherits from.  By doing this, views for inactive
 * states are accessible through locals object's protoypal inheritance chain from any state in the system.
 *
 * ---Transitions---
 * UI-Router Extras decorates the $state.transitionTo function.  While a transition is in progress, the toState and
 * fromState internal state representations are modified in order to coerce stock UI-Router's transitionTo() into performing
 * the appropriate operations.  When the transition promise is completed, the original toState and fromState values are
 * restored.
 *
 * Stock UI-Router's $state.transitionTo function uses toState.path and fromState.path to manage entering and exiting
 * states.  UI-Router Extras takes advantage of those internal implementation details and prepares a toState.path and
 * fromState.path which coerces UI-Router into entering and exiting the correct states, or more importantly, not entering
 * and not exiting inactive or sticky states.  It also replaces state.self.onEnter and state.self.onExit for elements in
 * the paths when they are being inactivated or reactivated.
 */



// ------------------------ Sticky State module-level variables -----------------------------------------------
var _StickyState; // internal reference to $stickyStateProvider
var internalStates = {}; // Map { statename -> InternalStateObj } holds internal representation of all states
var root, // Root state, internal representation
  pendingTransitions = [], // One transition may supersede another.  This holds references to all pending transitions
  pendingRestore, // The restore function from the superseded transition
  inactivePseudoState, // This pseudo state holds all the inactive states' locals (resolved state data, such as views etc)
  reactivatingLocals = { }, // This is a prent locals to the inactivePseudoState locals, used to hold locals for states being reactivated
  versionHeuristics = { // Heuristics used to guess the current UI-Router Version
    hasParamSet: false
  };

// Creates a blank surrogate state
function SurrogateState(type) {
  return {
    resolve: { },
    locals: {
      globals: root && root.locals && root.locals.globals
    },
    views: { },
    self: { },
    params: { },
    ownParams: ( versionHeuristics.hasParamSet ? { $$equals: function() { return true; } } : []),
    surrogateType: type
  };
}

// ------------------------ Sticky State registration and initialization code ----------------------------------
// Grab a copy of the $stickyState service for use by the transition management code
angular.module("ct.ui.router.extras.sticky").run(["$stickyState", function ($stickyState) {
  _StickyState = $stickyState;
}]);

angular.module("ct.ui.router.extras.sticky").config(
  [ "$provide", "$stateProvider", '$stickyStateProvider', '$urlMatcherFactoryProvider', 'uirextras_coreProvider',
    function ($provide, $stateProvider, $stickyStateProvider, $urlMatcherFactoryProvider, uirextras_coreProvider) {
      var core = uirextras_coreProvider;
      var internalStates = core.internalStates;
      var inherit = core.inherit;
      var inheritParams = core.inheritParams;
      var forEach = core.forEach;
      var map = core.map;
      var filterObj = core.filterObj;

      versionHeuristics.hasParamSet = !!$urlMatcherFactoryProvider.ParamSet;
      // inactivePseudoState (__inactives) holds all the inactive locals which includes resolved states data, i.e., views, scope, etc
      inactivePseudoState = angular.extend(new SurrogateState("__inactives"), { self: {  name: '__inactives'  } });
      // Reset other module scoped variables.  This is to primarily to flush any previous state during karma runs.
      root = pendingRestore = undefined;
      pendingTransitions = [];

      uirextras_coreProvider.onStateRegistered(function(state) {
        // Register the ones marked as "sticky"
        if (state.self.sticky === true) {
          $stickyStateProvider.registerStickyState(state.self);
        }
      });

      var $state_transitionTo; // internal reference to the real $state.transitionTo function
      // Decorate the $state service, so we can decorate the $state.transitionTo() function with sticky state stuff.
      $provide.decorator("$state", ['$delegate', '$log', '$q', function ($state, $log, $q) {
        // Note: this code gets run only on the first state that is decorated
        root = $state.$current;
        internalStates[""] = root;
        root.parent = inactivePseudoState; // Make inactivePsuedoState the parent of root.  "wat"
        inactivePseudoState.parent = undefined; // Make inactivePsuedoState the real root.
        // Add another locals bucket, as a parent to inactivatePseudoState locals.
        // This is for temporary storage of locals of states being reactivated while a transition is pending
        // This is necessary in some cases where $viewContentLoading is triggered before the $state.$current is updated to the toState.
        inactivePseudoState.locals = inherit(reactivatingLocals, inactivePseudoState.locals);
        root.locals = inherit(inactivePseudoState.locals, root.locals); // make root locals extend the __inactives locals.
        delete inactivePseudoState.locals.globals;

        // Hold on to the real $state.transitionTo in a module-scope variable.
        $state_transitionTo = $state.transitionTo;

        // ------------------------ Decorated transitionTo implementation begins here ---------------------------
        $state.transitionTo = function (to, toParams, options) {
          var DEBUG = $stickyStateProvider.debugMode();
          // TODO: Move this to module.run?
          // TODO: I'd rather have root.locals prototypally inherit from inactivePseudoState.locals
          // Link root.locals and inactives.locals.  Do this at runtime, after root.locals has been set.
          if (!inactivePseudoState.locals)
            inactivePseudoState.locals = root.locals;
          var idx = pendingTransitions.length;
          if (pendingRestore) {
            pendingRestore();
            if (DEBUG) {
              $log.debug("Restored paths from pending transition");
            }
          }

          var fromState = $state.$current, fromParams = $state.params;
          var rel = options && options.relative || $state.$current; // Not sure if/when $state.$current is appropriate here.
          var toStateSelf = $state.get(to, rel); // exposes findState relative path functionality, returns state.self
          var savedToStatePath, savedFromStatePath, stickyTransitions;
          var reactivated = [], exited = [], terminalReactivatedState;
          toParams = toParams || {};
          arguments[1] = toParams;

          var noop = function () {
          };
          // Sticky states works by modifying the internal state objects of toState and fromState, especially their .path(s).
          // The restore() function is a closure scoped function that restores those states' definitions to their original values.
          var restore = function () {
            if (savedToStatePath) {
              toState.path = savedToStatePath;
              savedToStatePath = null;
            }

            if (savedFromStatePath) {
              fromState.path = savedFromStatePath;
              savedFromStatePath = null;
            }

            angular.forEach(restore.restoreFunctions, function (restoreFunction) {
              restoreFunction();
            });
            // Restore is done, now set the restore function to noop in case it gets called again.
            restore = noop;
            // pendingRestore keeps track of a transition that is in progress.  It allows the decorated transitionTo
            // method to be re-entrant (for example, when superceding a transition, i.e., redirect).  The decorated
            // transitionTo checks right away if there is a pending transition in progress and restores the paths
            // if so using pendingRestore.
            pendingRestore = null;
            pendingTransitions.splice(idx, 1); // Remove this transition from the list
          };

          // All decorated transitions have their toState.path and fromState.path replaced.  Surrogate states also make
          // additional changes to the states definition before handing the transition off to UI-Router. In particular,
          // certain types of surrogate states modify the state.self object's onEnter or onExit callbacks.
          // Those surrogate states must then register additional restore steps using restore.addRestoreFunction(fn)
          restore.restoreFunctions = [];
          restore.addRestoreFunction = function addRestoreFunction(fn) {
            this.restoreFunctions.push(fn);
          };


          // --------------------- Surrogate State Functions ------------------------
          // During a transition, the .path arrays in toState and fromState are replaced.  Individual path elements
          // (states) which aren't being "kept" are replaced with surrogate elements (states).  This section of the code
          // has factory functions for all the different types of surrogate states.


          function stateReactivatedSurrogatePhase1(state) {
            var surrogate = angular.extend(new SurrogateState("reactivate_phase1"), { locals: state.locals });
            surrogate.self = angular.extend({}, state.self);
            return surrogate;
          }

          function stateReactivatedSurrogatePhase2(state) {
            var surrogate = angular.extend(new SurrogateState("reactivate_phase2"), state);
            var oldOnEnter = surrogate.self.onEnter;
            surrogate.resolve = {}; // Don't re-resolve when reactivating states (fixes issue #22)
            // TODO: Not 100% sure if this is necessary.  I think resolveState will load the views if I don't do this.
            surrogate.views = {}; // Don't re-activate controllers when reactivating states (fixes issue #22)
            surrogate.self.onEnter = function () {
              // ui-router sets locals on the surrogate to a blank locals (because we gave it nothing to resolve)
              // Re-set it back to the already loaded state.locals here.
              surrogate.locals = state.locals;
              _StickyState.stateReactivated(state);
            };
            restore.addRestoreFunction(function () {
              state.self.onEnter = oldOnEnter;
            });
            return surrogate;
          }

          function stateInactivatedSurrogate(state) {
            var surrogate = new SurrogateState("inactivate");
            surrogate.self = state.self;
            var oldOnExit = state.self.onExit;
            surrogate.self.onExit = function () {
              _StickyState.stateInactivated(state);
            };
            restore.addRestoreFunction(function () {
              state.self.onExit = oldOnExit;
            });
            return surrogate;
          }

          function stateEnteredSurrogate(state, toParams) {
            var oldOnEnter = state.self.onEnter;
            state.self.onEnter = function () {
              _StickyState.stateEntering(state, toParams, oldOnEnter);
            };
            restore.addRestoreFunction(function () {
              state.self.onEnter = oldOnEnter;
            });

            return state;
          }

          // TODO: This may be completely unnecessary now that we're using $$uirouterextrasreload temp param
          function stateUpdateParamsSurrogate(state, toParams) {
            var oldOnEnter = state.self.onEnter;
            state.self.onEnter = function () {
              _StickyState.stateEntering(state, toParams, oldOnEnter, true);
            };
            restore.addRestoreFunction(function () {
              state.self.onEnter = oldOnEnter;
            });

            return state;
          }

          function stateExitedSurrogate(state) {
            var oldOnExit = state.self.onExit;
            state.self.onExit = function () {
              _StickyState.stateExiting(state, exited, oldOnExit);
            };
            restore.addRestoreFunction(function () {
              state.self.onExit = oldOnExit;
            });

            return state;
          }


          // --------------------- decorated .transitionTo() logic starts here ------------------------
          if (toStateSelf) {
            var toState = internalStates[toStateSelf.name]; // have the state, now grab the internal state representation
            if (toState) {
              // Save the toState and fromState paths to be restored using restore()
              savedToStatePath = toState.path;
              savedFromStatePath = fromState.path;

              // Try to resolve options.reload to a state.  If so, we'll reload only up to the given state.
              var reload = options && options.reload || false;
              var reloadStateTree = reload && (reload === true ? savedToStatePath[0].self : $state.get(reload, rel));
              // If options.reload is a string or a state, we want to handle reload ourselves and not
              // let ui-router reload the entire toPath.
              if (options && reload && reload !== true)
                delete options.reload;

              var currentTransition = {
                toState: toState,
                toParams: toParams || {},
                fromState: fromState,
                fromParams: fromParams || {},
                options: options,
                reloadStateTree: reloadStateTree
              };

              pendingTransitions.push(currentTransition); // TODO: See if a list of pending transitions is necessary.
              pendingRestore = restore;

              // If we're reloading from a state and below, temporarily add a param to the top of the state tree
              // being reloaded, and add a param value to the transition.  This will cause the "has params changed
              // for state" check to return true, and the states will be reloaded.
              if (reloadStateTree) {
                currentTransition.toParams.$$uirouterextrasreload = Math.random();
                var params = reloadStateTree.$$state().params;
                var ownParams = reloadStateTree.$$state().ownParams;

                if (versionHeuristics.hasParamSet) {
                  var tempParam = new $urlMatcherFactoryProvider.Param('$$uirouterextrasreload');
                  params.$$uirouterextrasreload = ownParams.$$uirouterextrasreload = tempParam;
                  restore.restoreFunctions.push(function() {
                    delete params.$$uirouterextrasreload;
                    delete ownParams.$$uirouterextrasreload;
                  });
                } else {
                  params.push('$$uirouterextrasreload');
                  ownParams.push('$$uirouterextrasreload');
                  restore.restoreFunctions.push(function() {
                    params.length = params.length -1;
                    ownParams.length = ownParams.length -1;
                  });
                }
              }

              // $StickyStateProvider.processTransition analyzes the states involved in the pending transition.  It
              // returns an object that tells us:
              // 1) if we're involved in a sticky-type transition
              // 2) what types of exit transitions will occur for each "exited" path element
              // 3) what types of enter transitions will occur for each "entered" path element
              // 4) which states will be inactive if the transition succeeds.
              stickyTransitions = _StickyState.processTransition(currentTransition);

              if (DEBUG) debugTransition($log, currentTransition, stickyTransitions);

              // Begin processing of surrogate to and from paths.
              var surrogateToPath = toState.path.slice(0, stickyTransitions.keep);
              var surrogateFromPath = fromState.path.slice(0, stickyTransitions.keep);

              // Clear out and reload inactivePseudoState.locals each time transitionTo is called
              angular.forEach(inactivePseudoState.locals, function (local, name) {
                if (name.indexOf("@") != -1) delete inactivePseudoState.locals[name];
              });

              var saveViewsToLocals = function (targetObj) {
                return function(view, name) {
                  if (name.indexOf("@") !== -1) { // Only grab this state's "view" locals
                    targetObj[name] = view; // Add all inactive views not already included.
                  }
                }
              };

              // For each state that will be inactive when the transition is complete, place its view-locals on the
              // __inactives pseudostate's .locals.  This allows the ui-view directive to access them and
              // render the inactive views.
              forEach(stickyTransitions.inactives, function(state) {
                forEach(state.locals, saveViewsToLocals(inactivePseudoState.locals))
              });

              // For each state that will be reactivated during the transition, place its view-locals on a separate
              // locals object (prototypal parent of __inactives.locals, and remove them when the transition is complete.
              // This is necessary when we a transition will reactivate one state, but enter a second.
              // Gory details:
              //   - the entering of a new state causes $view.load() to fire $viewContentLoading while the transition is
              //     still in process
              //   - all ui-view(s) check if they should re-render themselves in response to this event.
              //   - ui-view checks if previousLocals is equal to currentLocals
              //     - it uses $state.$current.locals[myViewName] for previousLocals
              //   - Because the transition is not completed, $state.$current is set to the from state, and
              //     the ui-view for a reactivated state cannot find its previous locals.
              forEach(stickyTransitions.reactivatingStates, function(state) {
                forEach(state.locals, saveViewsToLocals(reactivatingLocals));
              });

              // When the transition is complete, remove the copies of the view locals from reactivatingLocals.
              restore.addRestoreFunction(function clearReactivatingLocals() {
                forEach(reactivatingLocals, function (val, viewname) {
                  delete reactivatingLocals[viewname];
                })
              });

              // Find all the states the transition will be entering.  For each entered state, check entered-state-transition-type
              // Depending on the entered-state transition type, place the proper surrogate state on the surrogate toPath.
              angular.forEach(stickyTransitions.enter, function (value, idx) {
                var surrogate;
                var enteringState = toState.path[idx];
                if (value === "reactivate") {
                  // Reactivated states require TWO surrogates.  The "phase 1 reactivated surrogates" are added to both
                  // to.path and from.path, and as such, are considered to be "kept" by UI-Router.
                  // This is required to get UI-Router to add the surrogate locals to the protoypal locals object
                  surrogate = stateReactivatedSurrogatePhase1(enteringState);
                  surrogateToPath.push(surrogate);
                  surrogateFromPath.push(surrogate);  // so toPath[i] === fromPath[i]

                  // The "phase 2 reactivated surrogate" is added to the END of the .path, after all the phase 1
                  // surrogates have been added.
                  reactivated.push(stateReactivatedSurrogatePhase2(enteringState));
                  terminalReactivatedState = enteringState;
                } else if (value === "reload") {
                  // If the state params have been changed, we need to exit any inactive states and re-enter them.
                  surrogateToPath.push(stateUpdateParamsSurrogate(enteringState));
                  terminalReactivatedState = enteringState;
                } else if (value === "enter") {
                  // Standard enter transition.  We still wrap it in a surrogate.
                  surrogateToPath.push(stateEnteredSurrogate(enteringState));
                }
              });

              // Find all the states the transition will be exiting.  For each exited state, check the exited-state-transition-type.
              // Depending on the exited-state transition type, place a surrogate state on the surrogate fromPath.
              angular.forEach(stickyTransitions.exit, function (value, idx) {
                var exiting = fromState.path[idx];
                if (value === "inactivate") {
                  surrogateFromPath.push(stateInactivatedSurrogate(exiting));
                  exited.push(exiting);
                } else if (value === "exit") {
                  surrogateFromPath.push(stateExitedSurrogate(exiting));
                  exited.push(exiting);
                }
              });

              // Add surrogate states for reactivated to ToPath again (phase 2), this time without a matching FromPath entry
              // This is to get ui-router to call the surrogate's onEnter callback.
              if (reactivated.length) {
                angular.forEach(reactivated, function (surrogate) {
                  surrogateToPath.push(surrogate);
                });
              }

              // We may transition directly to an inactivated state, reactivating it.  In this case, we should
              // exit all of that state's inactivated children.
              var orphans = stickyTransitions.orphans;
              // Add surrogate exited states for all orphaned descendants of the Deepest Reactivated State
              surrogateFromPath = surrogateFromPath.concat(map(orphans, function (exiting) {
                return stateExitedSurrogate(exiting);
              }));
              exited = exited.concat(orphans);

              // Replace the .path variables.  toState.path and fromState.path are now ready for a sticky transition.
              fromState.path = surrogateFromPath;
              toState.path = surrogateToPath;

              var pathMessage = function (state) {
                return (state.surrogateType ? state.surrogateType + ":" : "") + state.self.name;
              };
              if (DEBUG) $log.debug("SurrogateFromPath: ", map(surrogateFromPath, pathMessage));
              if (DEBUG) $log.debug("SurrogateToPath:   ", map(surrogateToPath, pathMessage));
            }
          }

          // toState and fromState are all set up; now run stock UI-Router's $state.transitionTo().
          var transitionPromise = $state_transitionTo.apply($state, arguments);

          // Add post-transition promise handlers, then return the promise to the original caller.
          return transitionPromise.then(function transitionSuccess(state) {
            // First, restore toState and fromState to their original values.
            restore();
            if (DEBUG)  debugViewsAfterSuccess($log, internalStates[state.name], $state);

            state.status = 'active';  // TODO: This status is used in statevis.js, and almost certainly belongs elsewhere.

            return state;
          }, function transitionFailed(err) {
            restore();
            if (DEBUG &&
              err.message !== "transition prevented" &&
              err.message !== "transition aborted" &&
              err.message !== "transition superseded") {
              $log.debug("transition failed", err);
              $log.debug(err.stack);
            }
            return $q.reject(err);
          });
        };
        return $state;
      }]);



      function debugTransition($log, currentTransition, stickyTransition) {
        function message(path, index, state) {
          return (path[index] ? path[index].toUpperCase() + ": " + state.self.name : "(" + state.self.name + ")");
        }

        var inactiveLogVar = map(stickyTransition.inactives, function (state) {
          return state.self.name;
        });
        var enterLogVar = map(currentTransition.toState.path, function (state, index) {
          return message(stickyTransition.enter, index, state);
        });
        var exitLogVar = map(currentTransition.fromState.path, function (state, index) {
          return message(stickyTransition.exit, index, state);
        });

        var transitionMessage = currentTransition.fromState.self.name + ": " +
          angular.toJson(currentTransition.fromParams) + ": " +
          " -> " +
          currentTransition.toState.self.name + ": " +
          angular.toJson(currentTransition.toParams);

        $log.debug("------------------------------------------------------");
        $log.debug("   Current transition: ", transitionMessage);
        $log.debug("Before transition, inactives are:   : ", map(_StickyState.getInactiveStates(), function (s) {
          return s.self.name;
        }));
        $log.debug("After transition,  inactives will be: ", inactiveLogVar);
        $log.debug("Transition will exit:  ", exitLogVar);
        $log.debug("Transition will enter: ", enterLogVar);
      }

      function debugViewsAfterSuccess($log, currentState, $state) {
        $log.debug("Current state: " + currentState.self.name + ", inactive states: ", map(_StickyState.getInactiveStates(), function (s) {
          return s.self.name;
        }));

        var viewMsg = function (local, name) {
          return "'" + name + "' (" + local.$$state.name + ")";
        };
        var statesOnly = function (local, name) {
          return name != 'globals' && name != 'resolve';
        };
        var viewsForState = function (state) {
          var views = map(filterObj(state.locals, statesOnly), viewMsg).join(", ");
          return "(" + (state.self.name ? state.self.name : "root") + ".locals" + (views.length ? ": " + views : "") + ")";
        };

        var message = viewsForState(currentState);
        var parent = currentState.parent;
        while (parent && parent !== currentState) {
          if (parent.self.name === "") {
            // Show the __inactives before showing root state.
            message = viewsForState($state.$current.path[0]) + " / " + message;
          }
          message = viewsForState(parent) + " / " + message;
          currentState = parent;
          parent = currentState.parent;
        }

        $log.debug("Views: " + message);
      }
    }
  ]
);

(function(angular, undefined) {
  var app = angular.module('ct.ui.router.extras.future', [ 'ct.ui.router.extras.core' ]);

  _futureStateProvider.$inject = [ '$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', 'uirextras_coreProvider' ];
  function _futureStateProvider($stateProvider, $urlRouterProvider, $urlMatcherFactory, uirextras_coreProvider) {
    var core = uirextras_coreProvider;
    var internalStates = core.internalStates;
    var stateFactories = {}, futureStates = {};
    var lazyloadInProgress = false, resolveFunctions = [], initPromise, initDone = false;
    var provider = this;

    // This function registers a promiseFn, to be resolved before the url/state matching code
    // will reject a route.  The promiseFn is injected/executed using the runtime $injector.
    // The function should return a promise.
    // When all registered promises are resolved, then the route is re-sync'ed.

    // Example: function($http) {
    //  return $http.get('//server.com/api/DynamicFutureStates').then(function(data) {
    //    angular.forEach(data.futureStates, function(fstate) { $futureStateProvider.futureState(fstate); });
    //  };
    // }
    this.addResolve = function (promiseFn) {
      resolveFunctions.push(promiseFn);
    };

    // Register a state factory function for a particular future-state type.  This factory, given a future-state object,
    // should create a ui-router state.
    // The factory function is injected/executed using the runtime $injector.  The future-state is injected as 'futureState'.

    // Example:
    //    $futureStateProvider.stateFactory('test', function(futureState) {
    //      return {
    //        name: futureState.stateName,
    //        url: futureState.urlFragment,
    //        template: '<h3>Future State Template</h3>',
    //        controller: function() {
    //          console.log("Entered state " + futureState.stateName);
    //        }
    //      }
    //    });
    this.stateFactory = function (futureStateType, factory) {
      stateFactories[futureStateType] = factory;
    };

    this.futureState = function (futureState) {
      if (futureState.stateName)  // backwards compat for now
        futureState.name = futureState.stateName;
      if (futureState.urlPrefix)  // backwards compat for now
        futureState.url = "^" + futureState.urlPrefix;

      futureStates[futureState.name] = futureState;
      var parentMatcher,  parentName = futureState.name.split(/\./).slice(0, -1).join("."),
        realParent = findState(futureState.parent || parentName);
      if (realParent) {
        parentMatcher = realParent.url || realParent.navigable && realParent.navigable.url;
      } else if (parentName === "") {
        parentMatcher = $urlMatcherFactory.compile("");
      } else {
        var futureParent = findState((futureState.parent || parentName), true);
        if (!futureParent) throw new Error("Couldn't determine parent state of future state. FutureState:" + angular.toJson(futureState));
        var pattern = futureParent.urlMatcher.source.replace(/\*rest$/, "");
        parentMatcher = $urlMatcherFactory.compile(pattern);
        futureState.parentFutureState = futureParent;
      }
      if (futureState.url) {
        futureState.urlMatcher = futureState.url.charAt(0) === "^" ?
          $urlMatcherFactory.compile(futureState.url.substring(1) + "*rest") :
          parentMatcher.concat(futureState.url + "*rest");
      }
    };

    this.get = function () {
      return angular.extend({}, futureStates);
    };

    function findState(stateOrName, findFutureState) {
      var statename = angular.isObject(stateOrName) ? stateOrName.name : stateOrName;
      return !findFutureState ? internalStates[statename] : futureStates[statename];
    }

    /* options is an object with at least a name or url attribute */
    function findFutureState($state, options) {
      if (options.name) {
        var nameComponents = options.name.split(/\./);
        if (options.name.charAt(0) === '.')
          nameComponents[0] = $state.current.name;
        while (nameComponents.length) {
          var stateName = nameComponents.join(".");
          if ($state.get(stateName, { relative: $state.current }))
            return null; // State is already defined; nothing to do
          if (futureStates[stateName])
            return futureStates[stateName];
          nameComponents.pop();
        }
      }

      if (options.url) {
        var matches = [];
        for(var future in futureStates) {
          var matcher = futureStates[future].urlMatcher;
          if (matcher && matcher.exec(options.url)) {
            matches.push(futureStates[future]);
          }
        }
        // Find most specific by ignoring matching parents from matches
        var copy = matches.slice(0);
        for (var i = matches.length - 1; i >= 0; i--) {
          for (var j = 0; j < copy.length; j++) {
            if (matches[i] === copy[j].parentFutureState) matches.splice(i, 1);
          }
        }
        return matches[0];
      }
    }

    function lazyLoadState($injector, futureState) {
      lazyloadInProgress = true;
      var $q = $injector.get("$q");
      if (!futureState) {
        var deferred = $q.defer();
        deferred.reject("No lazyState passed in " + futureState);
        return deferred.promise;
      }

      var parentPromises = $q.when([]), parentFuture = futureState.parentFutureState;
      if (parentFuture && futureStates[parentFuture.name]) {
        parentPromises = lazyLoadState($injector, futureStates[parentFuture.name]);
      }

      var type = futureState.type;
      var factory = stateFactories[type];
      if (!factory) throw Error("No state factory for futureState.type: " + (futureState && futureState.type));

      var failedLoadPolicy = factory.$options && factory.$options.failedLazyLoadPolicy || "remove";
      function deregisterFutureState() { delete(futureStates[futureState.name]); }
      function errorHandler(err) {
        if (failedLoadPolicy === "remove") deregisterFutureState();
        return $q.reject(err);
      }

      return parentPromises.then(function(array) {
        var factoryPromise = $injector.invoke(factory, factory, { futureState: futureState });

        return factoryPromise.then(function(fullState) {
          deregisterFutureState(); // Success; remove future state
          if (fullState) { array.push(fullState); } // Pass a chain of realized states back
          return array;
        });
      }).catch(errorHandler)
    }

    var otherwiseFunc = [ '$log', '$location',
      function otherwiseFunc($log, $location) {
        //$log.debug("Unable to map " + $location.path());
      }];

    function futureState_otherwise($injector, $location) {
      var resyncing = false;

      var lazyLoadMissingState =
        ['$rootScope', '$urlRouter', '$state',
          function lazyLoadMissingState($rootScope, $urlRouter, $state) {
            function resync() {
              resyncing = true; $urlRouter.sync(); resyncing = false;
            }
            if (!initDone) {
              // Asynchronously load state definitions, then resync URL
              initPromise().then(resync);
              initDone = true;
              return;
            }

            var futureState = findFutureState($state, { url: $location.path() });
            if (!futureState) {
              return $injector.invoke(otherwiseFunc);
            }

            // Config loaded.  Asynchronously lazy-load state definition from URL fragment, if mapped.
            lazyLoadState($injector, futureState).then(function lazyLoadedStateCallback(states) {
              states.forEach(function (state) {
                if (state && (!$state.get(state) || (state.name && !$state.get(state.name))))
                  $stateProvider.state(state);
              });
              lazyloadInProgress = false;
              resync();
            }, function lazyLoadStateAborted() {
              lazyloadInProgress = false;
              resync();
            });
          }];
      if (lazyloadInProgress) return;

      var nextFn = resyncing ? otherwiseFunc : lazyLoadMissingState;
      return $injector.invoke(nextFn);
    }

    $urlRouterProvider.otherwise(futureState_otherwise);

    $urlRouterProvider.otherwise = function(rule) {
      if (angular.isString(rule)) {
        var redirect = rule;
        rule = function () { return redirect; };
      }
      else if (!angular.isFunction(rule)) throw new Error("'rule' must be a function");
      otherwiseFunc = ['$injector', '$location', rule];
      return $urlRouterProvider;
    };

    var serviceObject = {
      getResolvePromise: function () {
        return initPromise();
      }
    };

    // Used in .run() block to init
    this.$get = [ '$injector', '$state', '$q', '$rootScope', '$urlRouter', '$timeout', '$log',
      function futureStateProvider_get($injector, $state, $q, $rootScope, $urlRouter, $timeout, $log) {
        function init() {
          $rootScope.$on("$stateNotFound", function futureState_notFound(event, unfoundState, fromState, fromParams) {
            if (lazyloadInProgress) return;
            //$log.debug("event, unfoundState, fromState, fromParams", event, unfoundState, fromState, fromParams);

            var futureState = findFutureState($state, { name: unfoundState.to });
            if (!futureState) return;

            event.preventDefault();
            var promise = lazyLoadState($injector, futureState);
            promise.then(function (states) {
              states.forEach(function (state) {
                if (state && (!$state.get(state) || (state.name && !$state.get(state.name))))
                  $stateProvider.state(state);
              });
              $state.go(unfoundState.to, unfoundState.toParams);
              lazyloadInProgress = false;
            }, function (error) {
              console.log("failed to lazy load state ", error);
              if (fromState.name) $state.go(fromState, fromParams);
              lazyloadInProgress = false;
            });
          });

          // Do this better.  Want to load remote config once, before everything else
          if (!initPromise) {
            var promises = [];
            angular.forEach(resolveFunctions, function (promiseFn) {
              promises.push($injector.invoke(promiseFn));
            });
            initPromise = function () {
              return $q.all(promises);
            };
          }

          // TODO: analyze this. I'm calling $urlRouter.sync() in two places for retry-initial-transition.
          // TODO: I should only need to do this once.  Pick the better place and remove the extra resync.
          initPromise().then(function retryInitialState() {
            $timeout(function () {
              if ($state.transition) {
                $state.transition.then(retryInitialState, retryInitialState);
              } else {
                $urlRouter.sync();
              }
            });
          });
        }

        init();

        serviceObject.state = $stateProvider.state;
        serviceObject.futureState = provider.futureState;
        serviceObject.get = provider.get;

        return serviceObject;
      }
    ];
  }

  app.provider('$futureState', _futureStateProvider);

  var statesAddedQueue = {
    state: function(state) {
      if (statesAddedQueue.$rootScope)
        statesAddedQueue.$rootScope.$broadcast("$stateAdded", state);
    },
    itsNowRuntimeOhWhatAHappyDay: function($rootScope) {
      statesAddedQueue.$rootScope = $rootScope;
    },
    $rootScope: undefined
  };

  app.config([ '$stateProvider', function($stateProvider) {
    // decorate $stateProvider.state so we can broadcast when a real state was added
    var realStateFn = $stateProvider.state;
    $stateProvider.state = function state_announce() {
      var val = realStateFn.apply($stateProvider, arguments);

      var state = angular.isObject(arguments[0]) ? arguments[0] : arguments[1];
      statesAddedQueue.state(state);
      return val;
    };
  }]);

  // inject $futureState so the service gets initialized via $get();
  app.run(['$futureState', function ($futureState, $rootScope) {
    statesAddedQueue.itsNowRuntimeOhWhatAHappyDay($rootScope);
  } ]);

})(angular);

angular.module('ct.ui.router.extras.previous', [ 'ct.ui.router.extras.core', 'ct.ui.router.extras.transition' ]).service("$previousState",
  [ '$rootScope', '$state', '$q',
    function ($rootScope, $state, $q) {
      var previous = null, lastPrevious = null, memos = {};

      $rootScope.$on("$transitionStart", function(evt, $transition$) {
        var from = $transition$.from;
        // Check if the fromState is navigable before tracking it.
        // Root state doesn't get decorated with $$state().  Doh.
        var fromState = from.state && from.state.$$state && from.state.$$state();
        function commit() { lastPrevious = null; }
        function revert() { previous = lastPrevious; }
        if (fromState) {
          lastPrevious = previous;
          previous = $transition$.from;

          $transition$.promise.then(commit)['catch'](revert);
        }
      });

      var $previousState = {
        get: function (memoName) {
          return memoName ? memos[memoName] : previous;
        },
        go: function (memoName, options) {
          var to = $previousState.get(memoName);
          if (memoName && !to) {
            return $q.reject(new Error('undefined memo'));
          }
          return $state.go(to.state, to.params, options);
        },
        memo: function (memoName, defaultStateName, defaultStateParams) {
          memos[memoName] = previous || { state: $state.get(defaultStateName), params: defaultStateParams };
        },
        forget: function (memoName) {
          if (memoName) {
            delete memos[memoName];
          } else {
            previous = undefined;
          }
        }
      };

      return $previousState;
    }
  ]
);

angular.module('ct.ui.router.extras.previous').run(['$previousState', function ($previousState) {
  // Inject $previousState so it can register $rootScope events
}]);


angular.module("ct.ui.router.extras.transition", [ 'ct.ui.router.extras.core' ]).config( [ "$provide",  function ($provide) {
      // Decorate the $state service, so we can replace $state.transitionTo()
      $provide.decorator("$state", ['$delegate', '$rootScope', '$q', '$injector',
        function ($state, $rootScope, $q, $injector) {
          // Keep an internal reference to the real $state.transitionTo function
          var $state_transitionTo = $state.transitionTo;
          // $state.transitionTo can be re-entered.  Keep track of re-entrant stack
          var transitionDepth = -1;
          var tDataStack = [];
          var restoreFnStack = [];

          // This function decorates the $injector, adding { $transition$: tData } to invoke() and instantiate() locals.
          // It returns a function that restores $injector to its previous state.
          function decorateInjector(tData) {
            var oldinvoke = $injector.invoke;
            var oldinstantiate = $injector.instantiate;
            $injector.invoke = function (fn, self, locals) {
              return oldinvoke(fn, self, angular.extend({$transition$: tData}, locals));
            };
            $injector.instantiate = function (fn, locals) {
              return oldinstantiate(fn, angular.extend({$transition$: tData}, locals));
            };

            return function restoreItems() {
              $injector.invoke = oldinvoke;
              $injector.instantiate = oldinstantiate;
            };
          }

          function popStack() {
            restoreFnStack.pop()();
            tDataStack.pop();
            transitionDepth--;
          }

          // This promise callback (for when the real transitionTo is successful) runs the restore function for the
          // current stack level, then broadcasts the $transitionSuccess event.
          function transitionSuccess(deferred, tSuccess) {
            return function successFn(data) {
              popStack();
              $rootScope.$broadcast("$transitionSuccess", tSuccess);
              deferred.resolve(data); // $transition$ deferred
              return data;
            };
          }

          // This promise callback (for when the real transitionTo fails) runs the restore function for the
          // current stack level, then broadcasts the $transitionError event.
          function transitionFailure(deferred, tFail) {
            return function failureFn(error) {
              popStack();
              $rootScope.$broadcast("$transitionError", tFail, error);
              deferred.reject(error);  // $transition$ deferred
              return $q.reject(error);
            };
          }

          // Decorate $state.transitionTo.
          $state.transitionTo = function (to, toParams, options) {
            // Create a deferred/promise which can be used earlier than UI-Router's transition promise.
            var deferred = $q.defer();
            // Place the promise in a transition data, and place it on the stack to be used in $stateChangeStart
            var tData = tDataStack[++transitionDepth] = {
              promise: deferred.promise
            };
            // placeholder restoreFn in case transitionTo doesn't reach $stateChangeStart (state not found, etc)
            restoreFnStack[transitionDepth] = function() { };
            // Invoke the real $state.transitionTo
            var tPromise = $state_transitionTo.apply($state, arguments);

            // insert our promise callbacks into the chain.
            return tPromise.then(transitionSuccess(deferred, tData), transitionFailure(deferred, tData));
          };

          // This event is handled synchronously in transitionTo call stack
          $rootScope.$on("$stateChangeStart", function (evt, toState, toParams, fromState, fromParams) {
              if (transitionDepth >= tDataStack.length) return;
              var depth = transitionDepth;
              // To/From is now normalized by ui-router.  Add this information to the transition data object.
              var tData = angular.extend(tDataStack[depth], {
                to: { state: toState, params: toParams },
                from: { state: fromState, params: fromParams }
              });

              var restoreFn = decorateInjector(tData);
              restoreFnStack[depth] = restoreFn;
              $rootScope.$broadcast("$transitionStart", tData);
            }
          );

          return $state;
        }]);
    }
  ]
);

// statevis requires d3.
(function () {
  "use strict";
  var app = angular.module("ct.ui.router.extras.statevis", [ 'ct.ui.router.extras.core', 'ct.ui.router.extras.sticky'  ]);

  app.directive('stateVis', [ '$state', '$timeout', '$interval', stateVisDirective ]);

  /**
   * This directive gets all the current states using $state.get() and displays them in a tree using D3 lib.
   * It then listens for state events and updates the tree.
   *
   * Usage:
   * <state-vis height="1000px" width="1000px"></state-vis>
   */
  function stateVisDirective($state, $timeout, $interval) {
    return {
      scope: {
        width: '@',
        height: '@'
      },
      restrict: 'AE',
      template: '<svg></svg>',
      link: function (_scope, _elem, _attrs) {
        var stateMap = {};
        var width = _scope.width || 400,
          height = _scope.height || 400;

        var tree = d3.layout.tree()
            .size([width - 20, height - 20])
            .separation(function (a, b) {
              return a.parent == b.parent ? 10 : 25;
            });

        var root = $state.get().filter(function (state) { return state.name === ""; })[0];
        var nodes = tree(root);

        root.parent = root;
        root.px = root.x = width / 2;
        root.py = root.y = height / 2;

        var activeNode = { };
        activeNode.px = activeNode.x = root.px;
        activeNode.py = activeNode.y = root.py;

        var diagonal = d3.svg.diagonal();

        var svg = d3.select(_elem.find("svg")[0])
          .attr("width", width)
          .attr("height", height)
          .append("g")
          .attr("transform", "translate(10, 10)");

        var node = svg.selectAll(".node"),
          link = svg.selectAll(".link"),
          active = svg.selectAll(".active")
          ;

        var updateInterval = 200,
          transLength = 200,
          timer = setInterval(update, updateInterval);

        function addStates(data) {
          // *********** Convert flat data into a nice tree ***************
          data = data.map(function (node) {
            return node.name === "" ? root : angular.copy(node);
          });
          angular.extend(stateMap, data.reduce(function (map, node) {
            map[node.name] = node;
            return map;
          }, {}));

          data.forEach(function (node) {
            // add to parent
            var parentName = node.name.split(/\./).slice(0, -1).join(".");
            var parent = node.name != parentName && stateMap[parentName];
            if (parent) {
              (parent.children || (parent.children = [])).push(node); // create child array if it doesn't exist
              node.px = parent.px;
              node.py = parent.py;
              nodes.push(node);
            }
          });
        }

        $interval(function () {
          _scope.states = $state.get();
          angular.forEach(nodes, function (n) {
            var s = $state.get(n.name);
            if (s) {
              n.status = s.status || 'exited';
            }
          });
//          _scope.futureStates = $futureState.get();
        }, 250);

        _scope.$watchCollection("states", function (newval, oldval) {
          var oldstates = (oldval || []).map(function (s) { return s.name; });
          addStates((newval || []).filter(function(state) { return oldstates.indexOf(state.name) == -1; } ));
//          addStates(_.reject(newval, function (state) { return _.contains(oldstates, state.name); }));
        });

//        addStates($state.get());
        update(updateInterval);

        function update() {
          // Recompute the layout and data join.
          node = node.data(tree.nodes(root), function (d) { return d.name; });
          link = link.data(tree.links(nodes), function (d) { return d.target.name; });
          active = active.data(activeNode);

          nodes.forEach(function (d) { d.y = d.depth * 70; });

          // Add entering nodes in the parent’s old position.
          var nodeEnter = node.enter();

          function stateName(node) {
            var name = node.name.split(".").pop();
            if (node.sticky) { name += " (STICKY)"; }
            if (node.deepStateRedirect) { name += " (DSR)"; }
            return name;
          }

          active.enter()
            .append("circle")
            .attr("class", "active")
            .attr("r", 13)
            .attr("cx", function (d) { return d.parent.px || 100; })
            .attr("cy", function (d) { return d.parent.py || 100; })
          ;

          nodeEnter.append("circle")
            .attr("class", "node")
            .attr("r", 9)
            .attr("cx", function (d) { return d.parent.px; })
            .attr("cy", function (d) { return d.parent.py; });

          nodeEnter.append("text")
            .attr("class", "label")
            .attr("x", function (d) { return d.parent.px; })
            .attr("y", function (d) { return d.parent.py; })
            .attr("text-anchor", function (d) { return "middle"; })
            .text(stateName)
            .style("fill-opacity", 1);


          // Add entering links in the parent’s old position.
          link.enter().insert("path", ".node")
            .attr("class", "link")
            .attr("d", function (d) {
              var o = {x: d.source.px, y: d.source.py};
              return diagonal({source: o, target: o});
            });

          // Transition nodes and links to their new positions.
          var t = svg.transition()
            .duration(transLength);

          t.selectAll(".link")
            .attr("d", diagonal);

          /* jshint -W093 */
          var circleColors = { entered: '#AF0', exited: '#777', active: '#0f0', inactive: '#55F', future: '#009' };
          t.selectAll(".node")
            .attr("cx", function (d) { return d.px = d.x; })
            .attr("cy", function (d) { return d.py = d.y; })
            .attr("r", function (d) { return d.status === 'active' ? 15 : 10; })
            .style("fill", function (d) { return circleColors[d.status] || "#FFF"; });

          t.selectAll(".label")
            .attr("x", function (d) { return d.px = d.x; })
            .attr("y", function (d) { return d.py = d.y - 15; })
            .attr("transform", function (d) { return "rotate(-25 " + d.x + " " + d.y + ")"; })
          ;

          t.selectAll(".active")
            .attr("x", function (d) { return d.px = d.x; })
            .attr("y", function (d) { return d.py = d.y - 15; });
        }
      }
    };
  }
})();


angular.module("ct.ui.router.extras",
  [
    'ct.ui.router.extras.core',
    'ct.ui.router.extras.dsr',
    'ct.ui.router.extras.future',
    'ct.ui.router.extras.previous',
    'ct.ui.router.extras.statevis',
    'ct.ui.router.extras.sticky',
    'ct.ui.router.extras.transition'
  ]);


}));