--[[----------------------------------------------------------------------------

WFFiniteStateMachine.lua

--------------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2015 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

------------------------------------------------------------------------------]]

--[[

	Provides functions that construct Finite Statement Machines
	(FSM) and functions that reveal information about a FSM.

	Note: WFFiniteStateMachine is very strict by design. It will raise
	errors if you try to construct an invalid state machine or try to ask the
	FSM about states or transitions it knows nothing about.

	In addition, WFFiniteStateMachine emits opaque "read only" objects by design.

	The "read only" contract isn't strict, it's possible to modify the FSM object
	by accessing the internals, but no API provide direct access for doing so.

--]]

	-- wf.core
local KSTableUtils = require "KSTableUtils"
local WFArguments = require 'WFArguments'
local WFArguments_check = assert( WFArguments.check, 
										"missing WFArguments.check" )
local WFFunctionUtils = require 'WFFunctionUtils'

	-- Rx
local Rx = require "Rx"

--------------------------------------------------------------------------------

local KSTableUtils_deepCopy = assert( KSTableUtils.deepCopy, 
										"missing KSTableUtils.deepCopy " )

--------------------------------------------------------------------------------

local WFFunctionUtils_reduce = assert( WFFunctionUtils.reduce,
										"WFFunctionUtils.reduce not defined" )

--------------------------------------------------------------------------------

local WFFunctionUtils_filter = assert( WFFunctionUtils.filter,
										"WFFunctionUtils.filter not defined" )

--------------------------------------------------------------------------------

local FSMClassPrototype = {

	_states = false,

	_stateIdentityMap = false,

	_transitions = false,

	_stateGroups = false,

}

--------------------------------------------------------------------------------

function FSMClassPrototype:__init()

	self._states = {}

	self._stateIdentityMap = {}

	self._transitions = {}

	self._stateGroups = {}

end

--------------------------------------------------------------------------------

local function deepCopyTableField( clone, field, orig )

	clone[ field ] = KSTableUtils_deepCopy( orig[ field ] )

	return clone

end

--------------------------------------------------------------------------------

local FSMClass -- fwd declare

--------------------------------------------------------------------------------

function FSMClassPrototype:_clone()

	local clone = FSMClass()

	local fieldsToCopy = { "_states", "_stateIdentityMap", "_transitions", 
								"_stateGroups" }

	return WFFunctionUtils_reduce( clone, 
									deepCopyTableField, 
										fieldsToCopy, self )

end

--------------------------------------------------------------------------------

local function addToIdentityMap( map, v )

	WFArguments_check( 'table', 'string' )

	assert( map[ v ] == nil, "state " .. v .. 
								" already in WFFiniteStateMachine" )

	map[ v ] = v

	return map

end

--------------------------------------------------------------------------------

local function addStatesToMap( stateMap, stateNameArray )

	return WFFunctionUtils_reduce( stateMap, addToIdentityMap, stateNameArray )

end

--------------------------------------------------------------------------------

local function addToArray( array, v )

	array[ #array + 1 ] = v

	return array

end

--------------------------------------------------------------------------------

local function addStatesToArray( stateArray, stateNameArray )

	return WFFunctionUtils_reduce( stateArray, addToArray, stateNameArray )

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_addStates( stateNameArray, maybeStateGroupName )

	self._stateIdentityMap = addStatesToMap( self._stateIdentityMap, 
												stateNameArray )

	self._states = addStatesToArray( self._states, stateNameArray )

	if maybeStateGroupName then

		self._stateGroups[ maybeStateGroupName ] = 
			self._stateGroups[ maybeStateGroupName ] or
			{
				states = {},
				stateMap = {},
			}

		local stateGroup = self._stateGroups[ maybeStateGroupName ] 

		stateGroup.states = addStatesToArray( 
								stateGroup.states, stateNameArray )

		stateGroup.stateMap = addStatesToMap( 
								stateGroup.stateMap, stateNameArray )

	end

	return self

end

--------------------------------------------------------------------------------

local function isStateValid( callerDesc, fsm, state )

	WFArguments_check( 'string', 'WFFiniteStateMachine', 'string' )

	if fsm._stateIdentityMap[ state ] == nil then

		return false, string.format( "invalid initial state '%s' %s", state,
										callerDesc )

	end

	return true

end

--------------------------------------------------------------------------------

local function checkStateValid( callerDesc, fsm, state )

	local ok, maybeErr = isStateValid( callerDesc, fsm, state )

	if not ok then

		error( maybeErr )

	end

end

--------------------------------------------------------------------------------

local function addTransition( transitions, transitionPair, state, 
								stateIdentityMap )

	assert( type( transitionPair ) == "table", "invalid transition pair entry" )

	local transition, newState = unpack( transitionPair )

	assert( type( transition ) == "string", 
			"invalid non string transition in transition pair" )

	assert( type( newState ) == "string", 
			"invalid non string state in transition pair" )

	assert( stateIdentityMap[ newState ],
				"invalid new state '" .. newState .. 
				"' in state transitionPair" )

	transitions[ state ] = transitions[ state ] or {}

	local stateTransitions = transitions[ state ]

	if stateTransitions[ transition ] then

		error( "the transition '" .. transition .. "' has already been defined " ..
				"to be '" .. stateTransitions[ transition ] .. 
				"' for state '" ..  state .. "'" )

	end

	stateTransitions[ transition ] = newState

	return transitions

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_addTransitions( state, transitionPairArray )

	checkStateValid( "_addTransitions initial state", self, state )

	self._transitions = WFFunctionUtils_reduce( 
								self._transitions,
								addTransition,
									transitionPairArray, state,
									self._stateIdentityMap )

	return self

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_peekNextState( state, transition )

	local stateOk, maybeStateErr = isStateValid( "_peekNextState initial state", 
													self, state )

	if not stateOk then

		return false, maybeStateErr

	end

	local maybeStateTransitions = self._transitions[ state ]

	if type( maybeStateTransitions ) ~= "table" then

		return false, string.format( "no transitions defined for state '%s'",
												state )

	end

	local nextState = maybeStateTransitions[ transition ]

	if not nextState then

		return false, string.format( "the transition '%s' is not defined for starting state '%s'",
										transition, state )

	end

	return nextState

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_getStateTransitions( state )

	checkStateValid( "_getStateTransitions state", self, state )

	local transitionsMap = self._transitions[ state ] or {}

	local transitions = {}

	for transition in pairs( transitionsMap ) do

		transitions[ #transitions + 1 ] = transition

	end

	return transitions

end

--------------------------------------------------------------------------------

local function maybeAddTransitionToState( transitions, state, transition )

	transitions[ state ] = transitions[ state ] or {}

	transitions[ state ][ transition ] = transitions[ state ][ transition ] or
											state

	return transitions

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_addTransitionAsNoOpUnlessSet( transition )

	self._transitions = WFFunctionUtils_reduce( 
							self._transitions,
								maybeAddTransitionToState,
									self._states, transition )

	return self

end

--------------------------------------------------------------------------------

local function isOrphaned( state, notOrphanedStates )

	return not notOrphanedStates[ state ] and state or nil

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_getOrphanedStates()

	local nonIdentityNextStates = {}

	for state, stateTransitions in pairs( self._transitions ) do

		for _, nextState in pairs( stateTransitions ) do

			if state ~= nextState then

				nonIdentityNextStates[ state ] = true

				nonIdentityNextStates[ nextState ] = true

			end

		end

	end

	return WFFunctionUtils_filter( isOrphaned,
									self._states, 
										nonIdentityNextStates )

end

--------------------------------------------------------------------------------

function FSMClassPrototype:_getSourceAndNextStatePairsForTransition( 
								transition )

	local sourceAndNextStatePairsForTransition = {}

	for state, stateTransitions in pairs( self._transitions ) do

		for transitionForCurrState, nextState in pairs( stateTransitions ) do

			if transitionForCurrState == transition then

				if state ~= nextState then

					sourceAndNextStatePairsForTransition[
						#sourceAndNextStatePairsForTransition + 1 ] =
							{ state, nextState }

				end

				break

			end

		end

	end

	return sourceAndNextStatePairsForTransition

end

--------------------------------------------------------------------------------

FSMClass = Rx.Class( "WFFiniteStateMachine", FSMClassPrototype )

--------------------------------------------------------------------------------
--[[!
	\brief Makes a new finite state machine (FSM)
	
	\return (WFFiniteStateMachine object) This object is opaque and has no
		public methods. It should simply be passed to other
		WFFiniteStateMachine API. 
--]]
--------------------------------------------------------------------------------

local function make( ... )

	assert( select( '#', ... ) == 0, "make takes no arguments" )

	return FSMClass()

end

--------------------------------------------------------------------------------
--[[!
	\brief Extends an existing FSM

	\param (WFFiniteStateMachine object) An exising FSM object

	\param (array) An array of state name strings.  Each element
		of state name array must be a unique name and also must not already exist
		in the FSM being extended. If a state name is duplicated, an error is
		raised.
	
	\return (WFFiniteStateMachine object) This object is opaque and has no
		public methods. It should simply be passed to other
		WFFiniteStateMachine API. 
--]]
--------------------------------------------------------------------------------

local function addStates( fsm, stateNameArray )

	WFArguments_check( 'WFFiniteStateMachine', 'table' )

	return fsm:_addStates( stateNameArray )

end

--------------------------------------------------------------------------------
--[[!
	\brief Modifies an existing FSM

	\param (WFFiniteStateMachine object) An exising FSM object

	\param maybeStateGroupName (optional string) The state group name is
		optional can be used to group common states together. 
		
	\param stateNameArray (array) An array of state name strings.  Each element
		of state name array must be a unique name and also must not already exist
		in the FSM being extended. If a state name is duplicated, an error is
		raised.
	
	\return (WFFiniteStateMachine object) This object is opaque and has no
		public methods. It should simply be passed to other
		WFFiniteStateMachine API. 
--]]
--------------------------------------------------------------------------------

local function addGroupedStates( fsm, stateGroupName, stateNameArray )

	WFArguments_check( 'WFFiniteStateMachine', 'string', 'table' )

	return fsm:_addStates( stateNameArray, stateGroupName )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an array of state names. The order of the names is the
		same as the order in which they were added.

	\param (WFFiniteStateMachine object) An exising FSM object
	
	\return stateNameArray (array) An array of state name strings in the same
		ordering as they were added. 
--]]
--------------------------------------------------------------------------------

local function getStates( fsm )

	WFArguments_check( 'WFFiniteStateMachine' )

	return KSTableUtils_deepCopy( fsm._states )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an identity map of state names. This can be used as 
		a "namespace" for the FSM states.

	\param (WFFiniteStateMachine object) An exising FSM object
	
	\return stateNameArray (table) A lua table. The (key, value) pair are
		identical strings representing a state name.
--]]
--------------------------------------------------------------------------------

local function getStateMap( fsm )

	WFArguments_check( 'WFFiniteStateMachine' )

	return KSTableUtils_deepCopy( fsm._stateIdentityMap )

end

--------------------------------------------------------------------------------

local function getStateGroup( fsm, stateGroupName )

	WFArguments_check( 'WFFiniteStateMachine', 'string' )

	return assert( fsm._stateGroups[ stateGroupName ],
						"WFFiniteStateMachine obj has no group named " .. 
						stateGroupName )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns a possibly empty array of state group names belonging to the
	fsm.

	\param (WFFiniteStateMachine object) An exising FSM object

	\return stateGroupNameArray (array) An array of state group name strings.
	If there are no state groups, the array will be empty.
--]]
--------------------------------------------------------------------------------

local function getStateGroupNames( fsm )
	
	
	local stateGroupNames = {}

	for stateGroupName in pairs( fsm._stateGroups ) do

		stateGroupNames[ #stateGroupNames + 1 ] = stateGroupName

	end

	return stateGroupNames

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an array of state names belonging to the state group. The
	order of the names is the same as the order in which they were added.

	\param (WFFiniteStateMachine object) An exising FSM object

	\param stateGroupName (string) 
	
	\return stateNameArray (array) An array of state name strings in the same
		ordering as they were added. 
--]]
--------------------------------------------------------------------------------

local function getStateGroupStates( fsm, stateGroupName )
	
	local stateGroup = getStateGroup( fsm, stateGroupName )

	return KSTableUtils_deepCopy( stateGroup.states )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an identity map of state names. This can be used as 
		a "namespace" for the FSM states.

	\param (WFFiniteStateMachine object) An exising FSM object

	\param stateGroupName (string) 
	
	\return stateNameArray (table) A lua table. The (key, value) pair are
		identical strings representing a state name.
--]]
--------------------------------------------------------------------------------

local function getStateGroupMap( fsm, stateGroupName )

	local stateGroup = getStateGroup( fsm, stateGroupName )

	return KSTableUtils_deepCopy( stateGroup.stateMap )

end

--------------------------------------------------------------------------------
--[[!
	\brief Makes a new finite state machine (FSM) by extending an existing FSM
		with transitions.

	\param (WFFiniteStateMachine object) An exising FSM object

	\param state (string) The name of state from which we are transitioning from.
		This state must already be in the FSM or an error will be raised.

	\param transitionPairArray (array of pairs) is an array of transitionPair
		tuples. A transitionPair tuple is a transition string and ending state
		string.  The transition string may only be defined once per state and
		the ending state string must already be in the FSM
	
	\return (WFFiniteStateMachine object) This object is opaque and has no
		public methods. It should simply be passed to other
		WFFiniteStateMachine API. It is also a completely different FSM
		than the one that is passed in.

--]]
--------------------------------------------------------------------------------

local function addTransitions( fsm, state, transitionPairArray )

	WFArguments_check( 'WFFiniteStateMachine', 'string', 'table' )

	return fsm:_addTransitions( state, transitionPairArray )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an array of all valid transition names from a state

	\param (WFFiniteStateMachine object) An exising FSM object

	\param state (string) The name of state from which we are transitioning from.
		This state must already be in the FSM or an error will be raised.

	\return (array) An array of all valid transition names that can be applied to
		the state. This array may be empty if no transitions are defined.

--]]
--------------------------------------------------------------------------------

local function getStateTransitions( fsm, state )

	WFArguments_check( 'WFFiniteStateMachine', 'string' )

	return fsm:_getStateTransitions( state )

end

--------------------------------------------------------------------------------
--[[!
	\brief Maybe returns the next state in a FSM given an initial state and
			transition.

	\param (WFFiniteStateMachine object) An exising FSM object

	\param state (string) The name of state from which we are transitioning from.

	\param transition (string) The name of transition to apply to the state. If
	
	\return (string_or_false, nil_or_string) The next state for this FSM after
		the transition has been applied to the initial state. If the next state
		could not be determined, false plus an error string are returned

--]]
--------------------------------------------------------------------------------

local function peekNextState( fsm, state, transition )

	WFArguments_check( 'WFFiniteStateMachine', 'string', 'string' )

	return fsm:_peekNextState( state, transition )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns the next state in a FSM given an initial state and transition.

	\param (WFFiniteStateMachine object) An exising FSM object

	\param state (string) The name of state from which we are transitioning from.
		This state must already be in the FSM or an error will be raised.

	\param transition (string) The name of transition to apply to the state. If
		the state has no ending state for a given transition, an error is raised.
	
	\return (string) The next state for this FSM after
		the transition has been applied to the initial state.

--]]
--------------------------------------------------------------------------------

local function getNextState( fsm, state, transition )

	WFArguments_check( 'WFFiniteStateMachine', 'string', 'string' )

	local nextState, maybeErr = 
			peekNextState( fsm, state, transition )

	if not nextState then

		error( maybeErr )

	end

	return nextState

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns state name represent the retry
	adding multiple states to a fsm

	\param (number) the number of times each of the states should be retried

	\param (vararg list of 1 or more strings) state names

	\return (vararg list of strings) Return is best illustrated by an example.
		Given ( 3, "a", "b" ), this will return "a", "b", "a2", "b2", "a3", "b3"
--]]
--------------------------------------------------------------------------------

local function getNthRetryStateName( nthTryCount, stateName )

	WFArguments_check( "number", "string" )

	assert( nthTryCount > 0, "must supply positive retry count" )

	return string.format( "%s%s", stateName, nthTryCount == 1 and 
												"" or tostring( nthTryCount ) )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns a var arg list of state names. Use this to as a terse way of
	adding multiple states to a fsm

	\param (number) the number of times each of the states should be retried

	\param (vararg list of 1 or more strings) state names

	\return (vararg list of strings) Return is best illustrated by an example.
		Given ( 3, "a", "b" ), this will return "a", "b", "a2", "b2", "a3", "b3"
--]]
--------------------------------------------------------------------------------

local function makeRetryableStates( retryCount, ... )

	WFArguments_check( "number" )

	assert( retryCount > 0, "must supply positive retry count" )

	local states = {}

	for i = 1, retryCount do

		for j = 1, select( "#", ... ) do

			states[ #states + 1 ] = getNthRetryStateName( i, select( j, ... ) )

		end

	end

	return unpack( states )

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an identity map of state names generated by 
		makeRetryableStates( retryCount, stateName)

	\param (number) the number of times each of the states should be retried

	\param (string) state names

	\return (vararg list of strings) Identity map of retry states
--]]
--------------------------------------------------------------------------------

local function makeRetryableStateMap( retryCount, stateName )

	WFArguments_check( "number", "string" )

	assert( retryCount > 0, "must supply positive retry count" )

	return addStatesToMap( {}, 
							{ makeRetryableStates( 
								retryCount, stateName ) } )

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds a retry transition loop to the fsm.

	\param (WFFiniteStateMachine) a state machine that contains all of the 
		states generated by 
		makeRetryableStates( retryCount, waitState, runningState )

	\param (number) the number of times each of the states should be retried

	\param (string) a state name representing a "waiting to run" state

	\param (string) a transition name representing a "ready to run" state

	\param (string) a state name representing a "currently running" state

	\param (string) a transition name representing a "schedule retry" state

	\return (WFFiniteStateMachine) A finite state machine that will support
		retryCount transitions from waitState to runningState
--]]
--------------------------------------------------------------------------------

local function addRetryTransitions( fsm, retryCount, waitState,
									scheduleTransition, runningState,
									retryTransition, noMoreRetriesState )

	WFArguments_check( "WFFiniteStateMachine", "number", "string", "string", 
							"string", "string", "string" )

	assert( retryCount > 0, "must supply positive retry count" )

	for i = 1, retryCount do

		local currWaitState = getNthRetryStateName( i, waitState )

		local currRunningState = getNthRetryStateName( i, runningState )

		fsm = addTransitions( fsm, currWaitState, 
								{ { scheduleTransition, currRunningState } } )

		local nextWaitState = 
				i < retryCount and 
				getNthRetryStateName( i + 1, waitState ) or noMoreRetriesState

		fsm = addTransitions( fsm, currRunningState, 
								{ { retryTransition, nextWaitState } } )

	end

	return fsm

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds the same transition to all retries of a fsm state

	\param (WFFiniteStateMachine) a state machine that contains all of the 
		states generated by makeRetryableStates( retryCount, retryState )

	\param (number) the number retries of the retryState

	\param (string) a state name representing state that is retried

	\param (string) a transition name 

	\param (string) a state name the effect of applying the transition to each
		of the retries of the state

	\return (WFFiniteStateMachine) The finite state machine will support
		transitions from each of the retries state to the requested state via
		the transition
--]]
--------------------------------------------------------------------------------

local function addTransitionToAllRetryStates( fsm, retryCount, retryState,
												transition, wantedState )

	WFArguments_check( "WFFiniteStateMachine", "number", "string", "string", 
							"string" )

	assert( retryCount > 0, "must supply positive retry count" )

	for i = 1, retryCount do

		local currRetryState = getNthRetryStateName( i, retryState )

		fsm = addTransitions( fsm, currRetryState, 
								{ { transition, wantedState } } )

	end

	return fsm

end

--------------------------------------------------------------------------------
--[[!
	\brief Adds a the same transition to all states unless it has been set

	\param (WFFiniteStateMachine) a state machine 

	\param (string) a transition name 

	\return (WFFiniteStateMachine) The same finite state machine that will now
		allow the transition on all states. If the transition already existed
		on the state, the next state is unchanged. If the transition did not
		exist for a state, it becomes a no op state. i.e. 
		getNextState( state, transition ) will return state.

--]]
--------------------------------------------------------------------------------

local function addTransitionAsNoOpUnlessSet( fsm, transition )

	WFArguments_check( "WFFiniteStateMachine", "string" )

	return fsm:_addTransitionAsNoOpUnlessSet( transition )

end

--------------------------------------------------------------------------------
--[[!
	\brief Checks the state machine and ensures there are no orphaned states. An
		orphaned state either has no transitions to it or only has identity 
		transitions (i.e. a transition that leaves the state unchanged)

	\param (WFFiniteStateMachine) a state machine 

	\return (array) This will array of orphaned state names.

--]]
--------------------------------------------------------------------------------

local function getOrphanedStates( fsm )

	WFArguments_check( "WFFiniteStateMachine" )

	return fsm:_getOrphanedStates()

end

--------------------------------------------------------------------------------
--[[!
	\brief Returns an array of { sourceState, nextState } pairs for a given
		transition. The sourceState values are all the states that accept
		the transition.

	\param (WFFiniteStateMachine) a state machine 

	\return (array) An array of arrays. The inner arrays have two elements, the
		source state and the next state.

--]]
--------------------------------------------------------------------------------
local function getSourceAndNextStatePairsForTransition( fsm, transition )

	WFArguments_check( "WFFiniteStateMachine", "string" )

	return fsm:_getSourceAndNextStatePairsForTransition( transition )

end

--------------------------------------------------------------------------------

local function returnNewFSMWithModifications( f )

	return function( fsm, ... )

		local newFSM = fsm:_clone()

		return f( newFSM, ... )

	end

end

	
--==============================================================================

return {

	make = make,

	addStates = addStates,

	addGroupedStates = addGroupedStates,

	addTransitions = addTransitions,

	addRetryTransitions = addRetryTransitions,

	addTransitionToAllRetryStates = addTransitionToAllRetryStates,

	addTransitionAsNoOpUnlessSet = addTransitionAsNoOpUnlessSet,

	makeNewWithAddedStates = returnNewFSMWithModifications( addStates ),

	makeNewWithAddedGroupedStates = 
		returnNewFSMWithModifications( addGroupedStates ),

	makeNewWithAddedTransitions = 
		returnNewFSMWithModifications( addTransitions ),

	makeNewWithAddedRetryTransitions = 
		returnNewFSMWithModifications( addRetryTransitions ),

	makeNewWithAddedTransitionToAllRetryStates = 
		returnNewFSMWithModifications( addTransitionToAllRetryStates ),

	makeNewWithAddedTransitionAsNoOpUnlessSet = 
		returnNewFSMWithModifications( addTransitionAsNoOpUnlessSet ),

	getStates = getStates,

	getStateMap = getStateMap,

	getStateGroupNames = getStateGroupNames,

	getStateGroupStates = getStateGroupStates,

	getStateGroupMap = getStateGroupMap,

	getStateTransitions = getStateTransitions,

	peekNextState = peekNextState,

	getNextState = getNextState,

	getNthRetryStateName = getNthRetryStateName,

	makeRetryableStates = makeRetryableStates,

	makeRetryableStateMap = makeRetryableStateMap,

	getOrphanedStates = getOrphanedStates,

	getSourceAndNextStatePairsForTransition =
		getSourceAndNextStatePairsForTransition,
}
