-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                http://www.curse.com/addons/wow/tradeskill-master               --
--                                                                                --
--             A TradeSkillMaster Addon (http://tradeskillmaster.com)             --
--    All Rights Reserved* - Detailed license information included with addon.    --
-- ------------------------------------------------------------------------------ --

local _, TSM = ...
TSMAPI_FOUR.FSM = {}
local private = { simpleTransitionEventHandlerCache = {} }
local FSM = TSMAPI_FOUR.Class.DefineClass("FSM")
local FSMState = TSMAPI_FOUR.Class.DefineClass("FSMState")



-- ============================================================================
-- FSM Class
-- ============================================================================

function FSM.__init(self, name)
	self._name = name
	self._currentState = nil
	self._context = nil
	self._stateObjs = {}
	self._defaultEvents = {}
end

function FSM.AddState(self, stateObj)
	assert(stateObj:__isa(FSMState))
	local name = stateObj:_GetName()
	assert(not self._stateObjs[name], "state already exists")
	self._stateObjs[stateObj:_GetName()] = stateObj
	return self
end

function FSM.AddDefaultEvent(self, event, handler)
	assert(not self._defaultEvents[event], "event already exists")
	self._defaultEvents[event] = handler
	return self
end

function FSM.Init(self, initialState, context)
	assert(self._stateObjs[initialState], "invalid initial state")
	self._currentState = initialState
	self._context = context or {}
	-- validate all the transitions
	for name, obj in pairs(self._stateObjs) do
		for _, toState in obj:_ToStateIterator() do
			assert(self._stateObjs[toState], format("toState doesn't exist (%s -> %s)", name, toState))
		end
	end
	return self
end

function FSM.ProcessEvent(self, event, ...)
	assert(self._currentState, "FSM not initialized")
	if self._handlingEvent then
		TSM:LOG_INFO("[%s] %s (ignored - handling event)", self._name, event)
		return
	elseif self._inTransition then
		TSM:LOG_INFO("[%s] %s (ignored - in transition)", self._name, event)
		return
	end

	TSM:LOG_INFO("[%s] %s", self._name, event)
	self._handlingEvent = true
	local currentStateObj = self._stateObjs[self._currentState]
	if currentStateObj:_HasEventHandler(event) then
		self:_Transition(TSMAPI_FOUR.Util.AcquireTempTable(currentStateObj:_ProcessEvent(event, self._context, ...)))
	elseif self._defaultEvents[event] then
		self:_Transition(TSMAPI_FOUR.Util.AcquireTempTable(self._defaultEvents[event](self._context, ...)))
	end
	self._handlingEvent = false
	return self
end

function FSM._Transition(self, eventResult)
	local result = eventResult
	while result[1] do
		-- perform the transition
		local currentStateObj = self._stateObjs[self._currentState]
		local toState = tremove(result, 1)
		local toStateObj = self._stateObjs[toState]
		TSM:LOG_INFO("[%s] %s -> %s", self._name, self._currentState, toState)
		assert(toStateObj and currentStateObj:_IsTransitionValid(toState), "invalid transition")
		self._inTransition = true
		currentStateObj:_Exit(self._context)
		self._currentState = toState
		result = TSMAPI_FOUR.Util.AcquireTempTable(toStateObj:_Enter(self._context, TSMAPI_FOUR.Util.UnpackAndReleaseTempTable(result)))
		self._inTransition = false
	end
	TSMAPI_FOUR.Util.ReleaseTempTable(result)
end



-- ============================================================================
-- FSMState Class
-- ============================================================================

function FSMState.__init(self, name)
	self._name = name
	self._onEnterHandler = nil
	self._onExitHandler = nil
	self._transitionValid = {}
	self._events = {}
end

function FSMState.SetOnEnter(self, handler)
	self._onEnterHandler = handler
	return self
end

function FSMState.SetOnExit(self, handler)
	self._onExitHandler = handler
	return self
end

function FSMState.AddTransition(self, toState)
	assert(not self._transitionValid[toState], "transition already exists")
	self._transitionValid[toState] = true
	return self
end

function FSMState.AddEvent(self, event, handler)
	assert(not self._events[event], "event already exists")
	self._events[event] = handler
	return self
end

function FSMState._GetName(self)
	return self._name
end

function FSMState._ToStateIterator(self)
	local temp = TSMAPI_FOUR.Util.AcquireTempTable()
	for toState in pairs(self._transitionValid) do
		tinsert(temp, toState)
	end
	return TSMAPI_FOUR.Util.TempTableIterator(temp)
end

function FSMState._IsTransitionValid(self, toState)
	return self._transitionValid[toState]
end

function FSMState._HasEventHandler(self, event)
	return self._events[event] and true or false
end

function FSMState._ProcessEvent(self, event, context, ...)
	return self._events[event](context, ...)
end

function FSMState._Enter(self, context, ...)
	if self._onEnterHandler then
		return self._onEnterHandler(context, ...)
	end
end

function FSMState._Exit(self, context)
	if self._onExitHandler then
		return self._onExitHandler(context)
	end
end



-- ============================================================================
-- TSMAPI Functions
-- ============================================================================

function TSMAPI_FOUR.FSM.New(name)
	return FSM(name)
end

function TSMAPI_FOUR.FSM.NewState(state)
	return FSMState(state)
end

function TSMAPI_FOUR.FSM.SimpleTransitionEventHandler(toState)
	if not private.simpleTransitionEventHandlerCache[toState] then
		private.simpleTransitionEventHandlerCache[toState] = function(context, ...)
			return toState, ...
		end
	end
	return private.simpleTransitionEventHandlerCache[toState]
end
