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

WFFiniteStateMachineTest.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.

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

local WFFiniteStateMachine = require 'WFFiniteStateMachine'

local WFRxTestCase = require 'WFRxTestCase'

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

local function asPointerStr( v )

	return string.format( "%p", v )

end

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

local function rxTestMake( rxTest )

	rxTest:expectError( WFFiniteStateMachine.make, "no args allowed" )

	local fsm1 = WFFiniteStateMachine.make()

	local fsm2 = WFFiniteStateMachine.make()

	-- Make sure empty state machines have same contents
	rxTest:expectEqual( fsm1, fsm2 )
	
	-- make sure state machines are unique pointers
	rxTest:expectNotEqual( asPointerStr( fsm1 ), asPointerStr( fsm2 ) )

end

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

local function rxTestAddStates( rxTest )

	local fsm = WFFiniteStateMachine.make()

	local abStateNameArray = { "a", "b" }

	local abFSM = WFFiniteStateMachine.makeNewWithAddedStates( fsm, abStateNameArray )

	rxTest:expectNotEqual( fsm, abFSM )

	local abFSM2 = WFFiniteStateMachine.makeNewWithAddedStates( fsm, abStateNameArray )
	
	-- make sure indentical contents generated
	rxTest:expectEqual( abFSM, abFSM2 )

	-- make sure state machines are unique pointers
	rxTest:expectNotEqual( asPointerStr( abFSM ), asPointerStr( abFSM2 ) )

	-- test add states with bogus args
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedStates, abFSM, "not array" )

	-- test adding existing states fails
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedStates, abFSM, abStateNameArray )

	-- test adding state 
	local abAFSM = WFFiniteStateMachine.makeNewWithAddedStates( abFSM, { "A" } )

	local expectedABAStates = { "a", "b", "A" }

	rxTest:expectEqual( WFFiniteStateMachine.getStates( abAFSM ),
							expectedABAStates )

end

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

local function rxTestAddGroupedStates( rxTest )

	local abStateGroupName = "ab"

	local abStateNameArray = { "a", "b" }

	local fsm = WFFiniteStateMachine.make()

	local abFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
										fsm, abStateGroupName, 
										abStateNameArray )

	-- test add states with bogus args
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedGroupedStates, 
												abFSM, { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedGroupedStates, 
												abFSM, "string", "not array" )

	-- test adding existing states fails
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedGroupedStates, 
												abFSM, abStateGroupName,
												abStateNameArray )

	-- test adding to state group is okay
	local abAFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
											abFSM, abStateGroupName, { "A" } )

	local expectedABAStates = { "a", "b", "A" }

	rxTest:expectEqual( 
		WFFiniteStateMachine.getStateGroupStates( abAFSM, abStateGroupName ), 
		expectedABAStates )


	-- test adding additional state groups
	local cdStateNameArray = { "c", "d" }

	local cdStateGroupName = "cdStates"

	local abcdFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
											abFSM, cdStateGroupName,
											cdStateNameArray )

	rxTest:expectNotEqual( abFSM, abcdFSM )

	local expectedStates = { "a", "b", "c", "d" }

	rxTest:expectEqual( WFFiniteStateMachine.getStates( abcdFSM ), 
							expectedStates )
	
	local expectedStateMap = { a = "a", b = "b", c = "c", d = "d" }

	rxTest:expectEqual( WFFiniteStateMachine.getStateMap( abcdFSM ), 
							expectedStateMap )

	local expectedABStates = { "a", "b" }

	rxTest:expectEqual( 
		WFFiniteStateMachine.getStateGroupStates( abcdFSM, abStateGroupName ), 
		expectedABStates )

	local expectedABStateMap = { a = "a", b = "b" }

	rxTest:expectEqual( 
		WFFiniteStateMachine.getStateGroupMap( abcdFSM, abStateGroupName ), 
		expectedABStateMap )

	local expectedCDStates = { "c", "d" }

	rxTest:expectEqual( 
		WFFiniteStateMachine.getStateGroupStates( abcdFSM, cdStateGroupName ), 
		expectedCDStates )

	local expectedCDStateMap = { c = "c", d = "d" }

	rxTest:expectEqual( 
		WFFiniteStateMachine.getStateGroupMap( abcdFSM, cdStateGroupName ), 
		expectedCDStateMap )

end

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

local function rxTestGetStateGroupNames( rxTest )

	local abStateGroupName = "ab"

	local abStateNameArray = { "a", "b" }

	local fsm = WFFiniteStateMachine.make()

	local fsmOneTwo = WFFiniteStateMachine.makeNewWithAddedStates( fsm, { "one", "two" } )

	rxTest:expectEqual( WFFiniteStateMachine.getStateGroupNames( fsmOneTwo ),
						{} )

	local abFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
										fsmOneTwo, abStateGroupName, 
										abStateNameArray )

	-- test adding additional state groups
	local cdStateNameArray = { "c", "d" }

	local cdStateGroupName = "cd"

	local abcdFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
											abFSM, cdStateGroupName,
											cdStateNameArray )

	rxTest:expectEqual( table.sort( WFFiniteStateMachine.getStateGroupNames( abcdFSM ) ), 
						table.sort( { "ab", "cd" } ) )

end

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

local function rxTestAddTransitions( rxTest )

	local fsm = WFFiniteStateMachine.make()

	local abStateGroupName = "abStates"

	local abStateNameArray = { "a", "b" }

	local abFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
											fsm, abStateGroupName,
											abStateNameArray )

	-- test add transitions with bogus args
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
							abFSM, { "not str" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
									abFSM, "a", "not array" )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
									abFSM, "a", { "not array of pairs " } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
									abFSM, "a", { { nil, "bad pair" } } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
									abFSM, "a", { { "t", "bad next state" } } )

	-- repeated transition
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitions, 
									abFSM, "a", { { "t", "a", }, { "t", "a" } } )

	local abFSM2 = WFFiniteStateMachine.makeNewWithAddedTransitions(
									abFSM, 
									"a", 
									{ { "1", "a" },
									  { "2", "b" },
									  { "3", "a" },
									  { "4", "b" },
									  { "5", "a" },
									} )

	-- given the above, verify that we get a new state machine after adding transition
	rxTest:expectNotEqual( abFSM, abFSM2 )

	local abFSM3 = WFFiniteStateMachine.makeNewWithAddedTransitions(
									abFSM2, 
									"a", 
									{ { "6", "b" } } )

	rxTest:expectNotEqual( abFSM2, abFSM3 )

	rxTest:expectError( WFFiniteStateMachine.getNextState,
						abFSM3, { "bad state" } )

	rxTest:expectError( WFFiniteStateMachine.getNextState,
						abFSM3, "unknown state" )

	rxTest:expectError( WFFiniteStateMachine.getNextState,
						abFSM3, "a", "unknown transition" )

	rxTest:expectError( WFFiniteStateMachine.getNextState,
						abFSM3, "a", { "bad transition" } )

	for i = 1, 6 do

		rxTest:expectEqual(
			WFFiniteStateMachine.getNextState( abFSM3, "a", tostring( i ) ),
			i % 2 == 1 and "a" or "b" )

	end

end

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

local function rxTestGetTransitions( rxTest )

	local fsm = WFFiniteStateMachine.make()

	local abStateGroupName = "abStates"

	local abStateNameArray = { "a", "b" }

	local abFSM = WFFiniteStateMachine.makeNewWithAddedGroupedStates( 
											fsm, abStateGroupName,
											abStateNameArray )

	-- test get transitions with bogus args
	rxTest:expectError( WFFiniteStateMachine.getStateTransitions, 
							abFSM, { "not str" } )

	rxTest:expectError( WFFiniteStateMachine.getStateTransitions, 
									abFSM, "invalid state" )

	rxTest:expectEqual( WFFiniteStateMachine.getStateTransitions( abFSM, "a" ),
							{} )

	local abFSM2 = WFFiniteStateMachine.makeNewWithAddedTransitions(
									abFSM, 
									"a", 
									{ { "1", "a" },
									  { "2", "b" },
									  { "3", "a" },
									  { "4", "b" },
									  { "5", "a" },
									} )

	rxTest:expectEqual( 
		table.sort( WFFiniteStateMachine.getStateTransitions( abFSM2, "a" ) ),
		table.sort( { "1", "2", "3", "4", "5" } ) )

end

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

local function rxTestAddRetryLoop( rxTest )

	local fsm = WFFiniteStateMachine.make()

	rxTest:expectError( WFFiniteStateMachine.makeRetryableStates, 0 )

	rxTest:expectError( WFFiniteStateMachine.makeRetryableStates, 
							1, { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeRetryableStates, 
							2, "a", { "not string" } )

	local abRetryStates = { WFFiniteStateMachine.makeRetryableStates( 
								3, "a", "b" ) }

	local expectedABRetryStates = { "a", "b", "a2", "b2", "a3", "b3" }

	rxTest:expectEqual( abRetryStates, expectedABRetryStates )

	-- bogus API use
	--
	--
	rxTest:expectError( WFFiniteStateMachine.getNthRetryStateName, 0 )

	rxTest:expectError( WFFiniteStateMachine.getNthRetryStateName,
							2, { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions, 
							fsm, 0 )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions, 
							fsm, 2, "a", { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions, 
							fsm, 2, "a", "ta", { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions, 
							fsm, 2, "a", "ta", "b", { "not string" } )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions, 
							fsm, 2, "a", "ta", "b", "tb", { "not string" } )

	local retryCount = 3

	local abFSM = WFFiniteStateMachine.makeNewWithAddedStates( fsm, abRetryStates )

	local abFSM2 = WFFiniteStateMachine.makeNewWithAddedStates( abFSM, { "fale" } )

	-- too many retries
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions,
							abFSM, retryCount + 1, "a", "do", "b", "retry",
							"fale"  )

	-- unsuppored retries
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedRetryTransitions,
							abFSM2, retryCount, "a", "do", "c", "retry", 
							"fale"  )

	local abFSM3 = WFFiniteStateMachine.makeNewWithAddedRetryTransitions(
							abFSM2, retryCount, "a", "do", "b", "retry", 
							"fale"  )

	for i = 1, retryCount do

		local currAState = WFFiniteStateMachine.getNthRetryStateName( i, "a" )

		local expectedBState = WFFiniteStateMachine.getNthRetryStateName( 
														i, "b" )
		
		rxTest:expectEqual( 
			WFFiniteStateMachine.getNextState( abFSM3, currAState, "do" ),
			expectedBState )


		local currBState = WFFiniteStateMachine.getNthRetryStateName( i, "b" )

		local expectedAState = i < retryCount and
				WFFiniteStateMachine.getNthRetryStateName( i + 1, "a" ) or
				"fale"

		rxTest:expectEqual( 
			WFFiniteStateMachine.getNextState( abFSM3, currBState, "retry" ),
			expectedAState )


	end

end

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

local function rxTestAddTransitionToAllRetryStates( rxTest )

	local fsm = WFFiniteStateMachine.make()

	local retryCount = 4

	local aRetryStates = { WFFiniteStateMachine.makeRetryableStates( 
													retryCount, "a" ) }

	local aFSM = WFFiniteStateMachine.makeNewWithAddedStates( fsm, aRetryStates )

	local abFSM = WFFiniteStateMachine.makeNewWithAddedStates( aFSM, { "b" } )

	-- too many retries
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitionToAllRetryStates,
							abFSM, retryCount + 1, "a", "do", "b" )

	-- unsuppored retries
	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitionToAllRetryStates,
							abFSM, retryCount + 1, "a", "do", "c" )

	local abFSM2 = WFFiniteStateMachine.makeNewWithAddedTransitionToAllRetryStates(
							abFSM, retryCount, "a", "do", "b" )

	for i = 1, retryCount do

		local currAState = WFFiniteStateMachine.getNthRetryStateName( i, "a" )

		rxTest:expectEqual( 
			WFFiniteStateMachine.getNextState( abFSM2, currAState, "do" ),
			"b" )

	end

end

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

local function rxTestMakeRetryableStateMap( rxTest )

	rxTest:expectError( WFFiniteStateMachine.makeRetryableStateMap, 0 )

	rxTest:expectError( WFFiniteStateMachine.makeRetryableStateMap, 
							1, { "not string" } )

	local retryAMap = WFFiniteStateMachine.makeRetryableStateMap( 3, "a" ) 

	rxTest:expectEqual( retryAMap, { a = "a", a2 = "a2", a3 = "a3" } )

end

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

local function rxTestAddTransitionAsNoOpUnlessSet( rxTest )

	local fsm = WFFiniteStateMachine.make()

	fsm = WFFiniteStateMachine.makeNewWithAddedStates( fsm, { "a", "b", "c", "d" } )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitions( fsm, "a", { { "bump", "b" } } )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitions( fsm, "b", { { "bump", "c" } } )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "a", "bump" ), "b" )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "b", "bump" ), "c" )

	rxTest:expectError( WFFiniteStateMachine.getNextState, fsm, "c", "bump" )

	rxTest:expectError( WFFiniteStateMachine.getNextState, fsm, "d", "bump" )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitionAsNoOpUnlessSet, fsm )

	rxTest:expectError( WFFiniteStateMachine.makeNewWithAddedTransitionAsNoOpUnlessSet, "bump" )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitionAsNoOpUnlessSet( fsm, "bump" )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "a", "bump" ), "b" )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "b", "bump" ), "c" )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "c", "bump" ), "c" )

	rxTest:expectEqual( WFFiniteStateMachine.getNextState( fsm, "d", "bump" ), "d" )

end

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

local function rxTestGetOrphanedStates( rxTest )

	local fsm = WFFiniteStateMachine.make()

	fsm = WFFiniteStateMachine.makeNewWithAddedStates( fsm, { "a", "b", "c", "d" } )

	rxTest:expectEqual( 
		table.sort( WFFiniteStateMachine.getOrphanedStates( fsm ) ),
		table.sort( { "a", "b", "c", "d" } ) )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitions( fsm, "a", { { "bump", "b" } } )

	rxTest:expectEqual( 
		table.sort( WFFiniteStateMachine.getOrphanedStates( fsm ) ),
		table.sort( { "b", "c", "d" } ) )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitions( fsm, "b", { { "bump", "c" } } )

	rxTest:expectEqual( 
		table.sort( WFFiniteStateMachine.getOrphanedStates( fsm ) ),
		table.sort( { "c", "d" } ) )

	fsm = WFFiniteStateMachine.makeNewWithAddedTransitionAsNoOpUnlessSet( fsm, "bump" )

	rxTest:expectEqual( 
		table.sort( WFFiniteStateMachine.getOrphanedStates( fsm ) ),
		table.sort( { "c", "d" } ) )

end


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

return WFRxTestCase.wrapSuite( 'WFFiniteStateMachineTest', {

	rxTestMake = rxTestMake,

	rxTestAddStates = rxTestAddStates,

	rxTestAddGroupedStates = rxTestAddGroupedStates,

	rxTestGetStateGroupNames = rxTestGetStateGroupNames,

	rxTestAddTransitions = rxTestAddTransitions,

	rxTestGetTransitions = rxTestGetTransitions,

	rxTestAddRetryLoop = rxTestAddRetryLoop,

	rxTestAddTransitionToAllRetryStates = rxTestAddTransitionToAllRetryStates,

	rxTestMakeRetryableStateMap = rxTestMakeRetryableStateMap,

	rxTestAddTransitionAsNoOpUnlessSet = rxTestAddTransitionAsNoOpUnlessSet,

	rxTestGetOrphanedStates = rxTestGetOrphanedStates,

} )

