extends VestTest

func get_suite_name() -> String:
	return "RewindableStateMachine"

var state_machine: RewindableStateMachine
var first_state: RewindableState
var other_state: RewindableState

func before_case(__):
	state_machine = RewindableStateMachine.new()
	first_state = mock(RewindableState)
	other_state = mock(RewindableState)
	
	# Setup mock answers
	when(first_state.can_enter).then_return(true)
	when(first_state.enter).then_answer(func(__): pass)
	when(first_state.exit).then_answer(func(__): pass)
	when(first_state.tick).then_answer(func(__): pass)
	when(first_state.display_enter).then_answer(func(__): pass)
	when(first_state.display_exit).then_answer(func(__): pass)
	
	when(other_state.can_enter).then_return(true)
	when(other_state.enter).then_answer(func(__): pass)
	when(other_state.exit).then_answer(func(__): pass)
	when(other_state.tick).then_answer(func(__): pass)
	when(other_state.display_enter).then_answer(func(__): pass)
	when(other_state.display_exit).then_answer(func(__): pass)
	
	# Set state names
	first_state.name = "First State"
	other_state.name = "Other State"
	
	# Add states as children - RSM will pick them up when added
	# **NOTE**: Make sure to set owner if spawning states manually, otherwise
	# RSM won't pick them up
	state_machine.add_child(first_state); first_state.owner = state_machine
	state_machine.add_child(other_state); other_state.owner = state_machine
	
	# TODO(vest): TestingSceneTree
	await Vest._scene_tree.process_frame
	Vest._scene_tree.root.add_child(state_machine, true)

func test_should_start_empty():
	expect_empty(state_machine.state)

func test_should_notify_new_state_on_enter():
	capture_signal(first_state.on_enter, 3)
	
	# Enter first state
	state_machine.transition("First State")
	
	# Check for event
	expect_not_empty(get_signal_emissions(first_state.on_enter))
	expect_empty(get_calls_of(first_state.can_enter))
	expect_equal(get_calls_of(first_state.enter), [[null, 0]])

func test_on_enter_should_prevent_transition():
	other_state.on_enter.connect(
		func(_new_state, _tick, prevent):
			prevent.call()
	)
	
	# Enter first state
	state_machine.transition("First State")
	
	# Try to enter second state
	expect_false(state_machine.transition("Other State"), "Transition should have failed!")
	expect_equal(state_machine.state, "First State")

func test_on_exit_should_prevent_transition():
	first_state.on_exit.connect(
		func(_new_state, _tick, prevent):
			prevent.call()
	)
	
	# Enter first state
	state_machine.transition("First State")
	
	# Try to enter second state
	expect_false(state_machine.transition("Other State"), "Transition should have failed!")
	expect_equal(state_machine.state, "First State")

func test_can_enter_should_prevent_transition():
	state_machine.state = "First State"
	
	# Register a more specific answer so the mock will use that
	when(other_state.can_enter)\
		.with_args([first_state])\
		.then_return(func(__): return false)
	
	# Try to enter second state
	expect_false(state_machine.transition("Other State"), "Transition should have failed!")
	expect_equal(state_machine.state, "First State")

func test_should_call_tick():
	capture_signal(first_state.on_tick, 3)

	# Set state
	state_machine.transition("First State")

	# Run a rollback tick
	state_machine._rollback_tick(0.16, 0, true)
	
	# Tick should have been called
	expect_equal(get_calls_of(first_state.tick), [[0.16, 0, true]], "Wrong method call!")
	expect_equal(get_signal_emissions(first_state.on_tick), [[0.16, 0, true]], "Wrong signal!")

func test_should_notify_display_state():
	state_machine.state = "First State"
	
	capture_signal(first_state.on_display_enter, 2)
	capture_signal(first_state.on_display_exit, 2)
	capture_signal(other_state.on_display_enter, 2)
	
	# First loop, display enters First State
	NetworkMocks.in_network_tick_loop(func():
		state_machine.transition("First State")
	)
	expect_not_empty(get_signal_emissions(first_state.on_display_enter))
	expect_not_empty(get_calls_of(first_state.display_enter))
	
	# Second loop, display enters Other State
	NetworkMocks.in_network_tick_loop(func():
		state_machine.transition("Other State")
	)
	
	expect_not_empty(get_signal_emissions(first_state.on_display_exit))
	expect_not_empty(get_signal_emissions(other_state.on_display_enter))

	expect_not_empty(get_calls_of(first_state.display_exit))
	expect_not_empty(get_calls_of(other_state.display_enter))

func after_case(__):
	state_machine.queue_free()
